/*    特徴    */

/*    教訓    */

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using ld  = long double;
using pint = pair<int, int>;
using pll = pair<ll, ll>;
using pld = pair<ld, ld>;
const int INF=1e9+7;
const ll LINF=9223372036854775807;
const ll MOD=1e9+7;
const ld PI=acos(-1);
const ld EPS = 1e-9; //微調整用（EPSより小さいと0と判定など）

int ii() { int x; if (scanf("%d", &x)==1) return x; else return 0; }
long long il() { long long x; if (scanf("%lld", &x)==1) return x; else return 0; }
string is() { string x; cin >> x; return x; }
char ic() { char x; cin >> x; return x; }
void oi(int x) { printf("%d ", x); }
void ol(long long x) { printf("%lld ", x); }
void od_nosp(double x) { printf("%.15f", x); } // 古い問題用
void od(double x) { printf("%.15f ", x); }
// long doubleで受け取り、fをLfなどに変えて出力すると、変な数値が出る
// それをなんとかするには独自の出力を作らなければならなそう
void os(const string &s) { printf("%s ", s.c_str()); }
void oc(const char &c) { printf("%c ", c); }
#define o_map(v){cerr << #v << endl; for(const auto& xxx: v){cout << xxx.first << " " << xxx.second << "\n";}} //動作未確認
void br() { putchar('\n'); }

#define gcd __gcd //llは受け取らない C++17~のgcdと違うので注意
int lcm(int a, int b){return a / gcd(a, b) * b;}
#define begin_end(a) a.begin(),a.end() //sort(begin_end(vec));
#define REP(i,m,n) for(ll i=(ll)(m) ; i < (ll) (n) ; i++ )
#define rep(i,n) REP(i,0,n)
#define m_p(a,b) make_pair(a,b)
#define SORT_UNIQUE(c) (sort(c.begin(),c.end()), c.resize(distance(c.begin(),unique(c.begin(),c.end()))))
#define GET_POS(c,x) (lower_bound(c.begin(),c.end(),x)-c.begin())
#define p_b push_back
#define SZ(x) ((int)(x).size) //size()がunsignedなのでエラー避けに

//4近傍（上下左右） rep(i, 2) にすると右・下だけに進む
vector<int> dx_4 = {1, 0, -1, 0};
vector<int> dy_4 = {0, 1, 0, -1};

// coutによるpairの出力（空白区切り）
template<typename T1, typename T2> ostream& operator<<(ostream& s, const pair<T1, T2>& p) {return s << "(" << p.first << " " << p.second << ")";}
// coutによるvectorの出力（空白区切り）
template<typename T> ostream& operator<<(ostream& s, const vector<T>& v) {
  int len = v.size();
  for (int i = 0; i < len; ++i) {
    s << v[i]; if (i < len - 1) s << " "; //"\t"に変えるとTabで見やすく区切る
  }
  return s;
}
// coutによる多次元vectorの出力（空白区切り）
template<typename T> ostream& operator<<(ostream& s, const vector< vector<T> >& vv) {
  int len = vv.size();
  for (int i = 0; i < len; ++i) {
    s << vv[i] << endl;
  }
  return s;
}

//最大値、最小値の更新。更新したor等しければtrueを返す
template<typename T>
bool chmax(T& a, T b){return (a = max(a, b)) == b;}
template<typename T>
bool chmin(T& a, T b){return (a = min(a, b)) == b;}

// -------- template end - //


// - library ------------- //

// BitMatrix 構造体の定義
// （MAX_ROW行 MAX_COL列 の2次元配列として扱える）
// 2次元配列と言っても、各行はvectorや配列でなく bitset<MAX_COL>
// BitMatrix[i] でi行目のbitset<MAX_COL>を読み書きできる
const int MAX_ROW = 200; // to be set appropriately l_e()を使うとき+1注意
const int MAX_COL = 65; // to be set appropriately (-->拡大係数行列のため)
// MAXはあくまでMAXであり、BitMatrix B(H, W); のH, Wは小さくてもよさそう（？）
// --> その場合、下のbitから使ってくれる
struct BitMatrix {
  int H, W;
  bitset<MAX_COL> val[MAX_ROW];
  BitMatrix(int m = 1, int n = 1) : H(m), W(n) {}
  inline bitset<MAX_COL>& operator [] (int i) {return val[i];}
};

// 受け取ったBitMatrix<MAX_COL>にガウスジョルダンの消去法を行い、破壊的に標準形に変える
// （注意！）出てくる標準形 BitMatrix は、上下反転しているので注意。
//          bitset[i] は「下からiビット目」を表し、右端から処理しているため
// 拡大係数行列のときは GJ(A, true) で呼び出す
int GaussJordan(BitMatrix &A, bool is_extended = false) {
  int rank = 0; // 現時点で何rank目まで行を固定したか
  // 列ごとにピボットのある行を見つけ、上に置く行から順番に固定していく
  // （注意！）右端からピボットを見つけていく（結果、得られる標準形は上下反転）
  for (int col = 0; col < A.W; ++col) {
    // ？　拡大係数行列の場合は最後の列は掃き出ししない
    if (is_extended && col == A.W - 1) break;

    // 固定済み行のすぐ下（rank行）から、ピボットを作れる行を探していく
    int pivot = -1;
    for (int row = rank; row < A.H; ++row) {
      if (A[row][col]) {
        pivot = row;
        break;
      }
    }
    // col列にピボットがなかったら次の列へ（rankは増えない）
    if (pivot == -1) continue;
    
    // col列にピボットを作れる行が見つかったので、固定済み行のすぐ下に固定
    // = 今見ている行 A[pivot]と、固定したい場所にある行 A[rank]をswap
    swap(A[pivot], A[rank]);

    // ピボットのある列の値がすべて 0 になるように掃き出す
    // rank行以外で、ピボットのある列の値が1になっているものをA[rank]で掃き出す
    for (int row = 0; row < A.H; ++row) {
      if (row != rank && A[row][col]) A[row] ^= A[rank];
    }
    ++rank;
  }
  return rank;
}

// F2上の連立一次方程式 Ax = b を解く（解なしの場合 -1 を返す）
// 解が一つに定まる場合（or解なし）を想定しているので、解が複数ある場合に注意
// （注意）拡大係数行列ではなく、l_e(左辺BitMatrix, 右辺vector<int>) で使う
// 元の行列A, ベクトルbは更新されない（解を求めるだけであり、階段行列にはしない）
// vector<int>の引数resに答えが入り、戻り値は rank
// b や res は {0, 1, 1, 0, 1} といった形式で渡す（戻ってくる）
int linear_equation(BitMatrix A, vector<int> b, vector<int> &res) {
  int m = A.H, n = A.W;
  // 行列A, ベクトルbを繋げて、拡大係数行列 M を作成
  // （注意！）ベクトルbを「左端」に繋げている！
  BitMatrix M(m, n + 1);
  for (int i = 0; i < m; ++i) {
    for (int j = 0; j < n; ++j) M[i][j] = A[i][j];
    M[i][n] = b[i]; // ベクトルbは「左端」に繋げる
  }
  // 拡大係数行列Mを標準形へと変形し、rankを求める
  int rank = GaussJordan(M, true);

  // check if it has no solution（解なしの場合-1を返す）
  // rank以降の行（解ありなら全部0になっているはずの行）に、000001 のような行があれば解なし（0x1+0x2+...+0xn=1 で矛盾）
  for (int row = rank; row < m; ++row) if (M[row][n]) return -1;

  // answer（返すのはrankなので、解の個数を知りたければ 2^(n-rank) ※n:=変数の個数=Aの列数
  // ※ GaussJordan()でもrankは求められるので都合で使い分ければいい
  // ベクトルbの部分が答えになる（n=rankの場合、そうなる）
  assert(n==rank); // 元々は無かったが、無いとやらかしそうなので入れた
  res.assign(n, 0);
  for (int i = 0; i < rank; ++i) res[n-1-i] = M[i][n]; // 改造あり
  return rank;
}

// --------- library end - //


int main(){

// あるターンにおけるxの2進表現(例えば01101)をベクトル(0,1,1,0,1)と捉える
// DP[i] := 「iターン目の直前に変数xがその状態であれば、人0が最終的にxを0にできる」というベクトルの集合。

/* 前提の整理


  0と1しかないF2({0, 1})の世界（XORの世界、mod2の世界ともいえる）
  この世界の「+（加法）」は XOR である（0と1しかない世界なので、1に1を足しても上が無く、0に戻ってしまう＝XORの動きと同じ）

  F2は体（有限体）である
  加法・乗法について閉じている（足しても掛けても0, 1以外にならない）、逆元（足して0、掛けて0になるもの）が定義できる、分配法則が成り立つなどの条件を満たしているため

  体なので、線形代数学における基底にまつわる定理などを適用することができる
  （具体的には？）--> 基底に含まれるベクトルの線形結合によって、元のベクトル空間（ベクトルの集合）に含まれる全てのベクトルを生成できる
  つまり、「基底->ベクトル空間, ベクトル空間->基底」という変換が可逆的
  これを利用して、膨大な集合である DP[i] をそのまま扱わず、基底に変換して管理することができるようになる！
　※実装上、DP[N]={0}を入れるが、これは基底ではないので注意が必要（最後のターンが人1のターンとなるコーナーケースに関係してくる）

  各ターンの DP[i] は本当にベクトル空間（基底から生成できる「全ての」ベクトルを含む集合。もちろん基底から生成できないベクトルは含まない）なのか？
  ・人1は何もできない or 空集合にする（人1の勝ちが確定）だけなので、人0が連続する場合を、ゲーム終了後から遡って考える（数学的帰納法のつもり）
  ・DP[N-1]（ゲーム終了後から1ターン遡った状態）: {aN-1}が生成する部分空間{0, aN-1} -> そこから先、次のaが一次従属ならDP変化なし（既に部分空間だから）。では次のaが一次独立なら？
  -> ターンを1遡る（今あるDPは残したまま、DPにxor a を加える）ことは、「あらゆるλ（0か1）の組合せについての(λ1a1+...+λpap)={a1, ..., ap}が生成する部分空間V の全てのベクトル および その全ての要素について xor a をしたベクトルの集合」と言える。この集合は{a1, ..., ap, a}から生成できる全てのベクトルの集合（数が2倍になっていることを考えると分かりやすい）なので、新しいDP[i]は次元p+1のベクトル空間と示せる！

*/

// ＜人1が「何もできない or 空集合にする」のはなぜか？＞
// 人1がiターン目に使えるベクトルをAiとする
// Ai ∉ DPなら、Ai はそのターンより後に人0が使うことができるベクトルに含まれない、新しい次元を持つベクトル（基底の次元を増やすようなベクトル）である
// ということは、人1としては、その「新しい次元」方向にベクトルを伸ばした状態で人0にターンを回せばいい。人0はそのベクトルを相殺できないので、人0の勝利が決まる
// ＜人1の最適行動＞
// (1) 人1に回されたベクトルが、Aiがもつ「新しい次元」方向のベクトルと同じ方向に飛び出たベクトルなら、そのベクトルを残したまま（つまりx^Aiで置き換えずに）回す
// (2) 人1に回されたベクトルが「新しい次元」方向に伸びていないベクトルなら、x^Aiで置き換えて（つまり、「新しい次元」方向にベクトルを伸ばして）回す

  ll T = il();

  rep(i, T){
    bool hito1_win = false;
    ll N = il();
    vector<ll> As;
    rep(i, N){
      As.push_back(il());
    }
    string S = is();
    vector<ll> Ss;
    rep(i, N){
      Ss.p_b((S[i] == '0' ? 0 : 1));
    }

    // iターン目の直前にそうであったら人0が勝てる集合
    // 0-indexed
    // DP[i].H でDP[i]に含まれるベクトル数が分かる
    vector<BitMatrix> DP(N+1);

    // DP[N] は {0}
    DP[N] = BitMatrix(1, MAX_COL);
    DP[N][0] = 0;

    // N-j = N-1 ~ 0 についてループ
    // 人1勝利になったらbreak
    // S ∈ DP[0] なら人0の勝ち（最終的にxが0になる）
    REP(j, 1, N+1){
      bitset<MAX_COL> abitset = As[N-j];

      // DP[N-j+1] に As[i] を加えた集合
      BitMatrix tmp_bitmatr(DP[N-j+1].H + 1, MAX_COL);
      rep(k, DP[N-j+1].H) tmp_bitmatr[k] = DP[N-j+1][k];
      tmp_bitmatr[DP[N-j+1].H] = abitset;

      // その基底をとり、新しいrankを判定
      ll newrank = GaussJordan(tmp_bitmatr);

      // 人1の場合
      if (Ss[N-j]==1){
        if (N-j==N-1){
          // 最後のターンが人1であった場合なので、無条件で人1勝利
          // このコーナーケースを処理しない場合、DP[N]の次元が1と判定されてしまい、次元が増えていないように見えてしまうのでバグが出る
          // DP[N]={0} が本来基底ではないことが原因。
          cout << 1 << endl;
          hito1_win = true;
          break;
        }
        // abitsetが新しい次元のベクトルであり、人0が勝つ場合が確定するかどうか
        // それは、DP[N-j+1]に新しいベクトルを加えて、rankが増えたかどうかで分かる
        if (newrank > DP[N-j+1].H){
          cout << 1 << endl;
          hito1_win = true;
          break;
        }
        // 勝利できなかったので、DP変化なし
        DP[N-j] = DP[N-j+1];        
      }
      if (hito1_win) break; 

      // 人0の場合、新しい基底がそのまま次のDPとなる
      // ただし、ゼロベクトルの行がゴミとして残っているので、それは削除する（newrank行目までDP[N-j]にコピーして、それより下の行は捨てる）
      // 新しい次元が増えなかった場合、tmp_bitmatr=DP[N-j+1]なので、変化なし
      DP[N-j] = BitMatrix(newrank, MAX_COL);
      rep(k, newrank) DP[N-j][k] = tmp_bitmatr[k];

    }
    // 人1が勝利していたら、0を表示せず次のターンへ
    if (hito1_win) continue;

    // DP[0]が求まったので、xの初期値=0が人0勝利条件のベクトル集合に含まれるかどうか判定
    // ……は実はしなくても、途中で人1が勝利していなければ0ベクトルは絶対にはじき出されていないので、人0の勝利が確定する
    cout << 0 << endl;

  }


  
}

Measured memory_bytes: 4324