けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

yukicoder No.803 Very Limited Xor Subset

F2 線形代数大好き!

問題へのリンク

問題概要

 N 個の整数  A_1, A_2, \dots, A_N の部分集合を選んで XOR 和を  X にする方法のうち、 M 個の区間 [  L_i, R_i ] ( 1 \le L_i \le R_i \le N) について

  • 区間の中から選ぶ個数は偶数個か奇数個かが指定される

という制約を満たすものが何通りあるか求めよ。

制約

  •  1 \le N \le 300
  •  0 \le M \le 300

考えたこと

実は諸事情あって、このセットに F2 線形代数が出ることは知ってたのだけど、まさか僕が 3 日前にふと TL に放流した XOR 和を題材とした問題 (それも想定解法は F2 線形代数) と、題材まで同じだったとは思わなかった。

以下のような  N 個の F2 上の変数を用意する:

  •  x_i = 1 (選ぶとき), 0 (選ばないとき)

これを用意すると、実は綺麗な連立方程式が立てられます!!!!!!!!!!!!!!!!!!!

ここで有力なのは、「XOR 和は各桁ごとに F2 の足し算演算をしたものである」という見方です。その見方をすることで、各桁ごとに大体 31 桁分くらい、F2 上の一次方程式を立てることができます。

そして区間制約も実はそのまま扱うことができて、

  • 偶数個である: F2 上の総和が 0
  • 奇数個である: F2 上の総和が 1

という風に自然に扱うことができる。よって問題は F2 上の連立一次方程式を解くことに帰着し、その解の個数は

  • 自由度が、(変数の個数) - (行列のランク) で求められる
  • 解の個数は  2^{(自由度)} で求められる

という流れで求められる。

#include <iostream>
#include <vector>
#include <bitset>
using namespace std;
const long long MOD = 1000000007;

// BitMatrix
const int MAX_ROW = 610; // to be set
const int MAX_COL = 610; // to be set
struct BitMatrix {
    int n, m;
    bitset<MAX_COL> val[MAX_ROW];
    BitMatrix(int n_ = 1, int m_ = 1) {n = n_; m = m_;}
    inline bitset<MAX_COL>& operator [] (int i) {return val[i];}
    inline friend ostream& operator << (ostream& s, BitMatrix M) {
        s << endl;
        for (int i = 0; i < M.n; ++i) {
            for (int j = 0; j < M.m; ++j) s << M.val[i][j];
            s << endl;
        }
        return s;
    }
};

inline BitMatrix operator * (BitMatrix A, BitMatrix B) {
    BitMatrix R(A.n, B.m);
    BitMatrix tB(B.m, B.n);
    for (int i = 0; i < tB.n; ++i) for (int j = 0; j < tB.m; ++j) tB[i][j] = B[j][i];
    for (int i = 0; i < R.n; ++i) for (int j = 0; j < R.m; ++j) R[i][j] = (A[i] & tB[j]).any();
    return R;
}

int linear_equation(BitMatrix A, vector<int> b) {
    int rank = 0;
    for (int i = 0; i < A.n; ++i) { A[i][A.m] = b[i]; }

    for (int i = 0; i < A.m; ++i) {
        int pivot = -1;
        for (int j = rank; j < A.n; ++j) {
            if (A[j][i]) {
                pivot = j;
                break;
            }
        }
        if (pivot != -1) {
            swap(A[pivot], A[rank]);
            for (int j = 0; j < A.n; ++j) if (j != rank && A[j][i]) A[j] ^= A[rank];
            ++rank;
        }
    }

    for (int i = rank; i < A.n; ++i) if (A[i][A.m]) return -1; // 解なし   
    return rank;
};

long long modpow(long long a, long long n, long long mod) {
    long long res = 1;
    while (n > 0) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}



const int DIGIT = 35;
int main() {
    int N, M; long long X;
    cin >> N >> M >> X;
    vector<long long> a(N);
    for (int i = 0; i < N; ++i) cin >> a[i];
    BitMatrix A(DIGIT + M, N);
    vector<int> b(DIGIT + M);
    for (int d = 0; d < DIGIT; ++d) {
        for (int i = 0; i < N; ++i) {
            if (a[i] & (1LL<<d)) A[d][i] = 1;
        }
        if (X & (1LL<<d)) b[d] = 1;
    }
    for (int d = 0; d < M; ++d) {
        int type, l, r; cin >> type >> l >> r;
        --l, --r;
        for (int i = l; i <= r; ++i) A[d + DIGIT][i] = 1;
        b[d + DIGIT] = type;
    }
    int rank = linear_equation(A, b);
    if (rank == -1) cout << 0 << endl;
    else cout << modpow(2LL, N-rank, MOD) << endl;
}