E - 通信モードの切り替え / Switching Communication Modes 解説 by admin 印刷ページ
claude4.8opus-high概要
各送信機をモード A・B のどちらかに設定したとき、選んだ信号値の XOR ですべての目標信号値 \(T_j\) を作れるような設定の数を数える問題です。\(N \leq 15\) と小さいので、\(2^N\) 通りの設定をすべて試すことができます。
考察
まず、「いくつかの送信機を稼働させたときの XOR」で作れる値の集合について考えます。\(N\) 個の値 \(a_1, a_2, \ldots, a_N\) から、好きな部分集合を選んで XOR を取って作れる値の全体は、これらの値が張る線形空間(XOR 空間)になります。
ここで重要なのは次の観察です。
ある目標値 \(T_j\) が「部分集合の XOR で作れる」かどうかは、\(\{a_1, \ldots, a_N\}\) が張る XOR 空間に \(T_j\) が含まれるかどうかと同値である。
XOR は GF(2)(2 元体)上のベクトル演算とみなせるため、線形代数の考え方がそのまま使えます。値を 60 ビットのベクトルとみなし、「掃き出し法(ガウスの消去法)」で基底(XOR 基底 / linear basis)を作れば、
- ある値 \(T\) が空間に含まれる ⟺ \(T\) を基底で消去していったとき最終的に \(0\) になる
という判定ができます。
素朴に「各 \(T_j\) について部分集合 \(2^N\) 通りを全探索」してしまうと、設定 \(2^N\) 通り × 目標 \(M\) 個 × 部分集合 \(2^N\) 通りとなり非常に重くなります。XOR 基底を使えば、各目標の判定が高速(ビット数程度)に行えるため、効率よく処理できます。
アルゴリズム
モードの割り当ては \(2^N\) 通り。各送信機 \(i\) について
maskの \(i\) ビット目が立っていれば \(W_i\)、立っていなければ \(V_i\) を使う、というようにmask(\(0\) 以上 \(2^N\) 未満の整数)で全パターンを列挙します。各
maskについて、選ばれた \(N\) 個の値から XOR 基底を構築します。basis[b]を「最上位ビットが \(b\) である基底ベクトル」とします。- 値 \(x\) を上位ビットから見ていき、立っているビット \(b\) について
basis[b]がまだ空なら \(x\) をそこに登録、そうでなければ \(x \mathbin{\oplus}= \mathrm{basis}[b]\) として消去を続けます。
すべての目標値 \(T_j\) について、同じ要領で基底を使って \(T_j\) を消去します。
- 消去後に \(0\) になれば「作れる」、\(0\) にならなければ「作れない」。
- 1 つでも作れない目標があれば、その
maskは条件を満たしません。
条件を満たす
maskの個数を数え、\(10^9+7\) で割った余りを出力します。
具体例として、\(V = (3, 5)\) で両方モード A を選んだとします。\(3 = (011)_2\), \(5 = (101)_2\) が張る空間には \(3 \oplus 5 = 6\) や \(0\) も含まれ、\(\{0, 3, 5, 6\}\) が作れます。目標 \(T = 6\) なら、基底で消去すると \(0\) になるので「作れる」と判定されます。
計算量
ビット幅を \(B = 60\) とします。
各
maskごとに基底構築が \(O(N \cdot B)\)、目標判定が \(O(M \cdot B)\)。これを \(2^N\) 通り行うので、
時間計算量: \(O(2^N \cdot (N + M) \cdot B)\)
空間計算量: \(O(N + M + B)\)(基底配列はビット幅ぶんで定数サイズ)
\(N \leq 15\), \(M \leq 100\), \(B = 60\) より十分高速に間に合います。
実装のポイント
信号値は最大 \(2^{60}-1\) なので、必ず 64 ビット整数(
unsigned long long)で扱います。ビットを取り出す際のシフトも1ULL << bのように 64 ビットで行うことに注意してください。各
maskごとに基底配列を必ず初期化(ゼロクリア)してから構築します。答えを数える際、\(2^{15} = 32768\) 通りしかないので途中でオーバーフローの心配はほぼありませんが、最後に MOD を取っておけば安全です。
空集合の XOR は \(0\) であり、これは常に基底で消去して \(0\) になるため、\(T_j = 0\) の目標は自動的に「作れる」と判定されます(特別扱い不要)。
ソースコード
#include <bits/stdc++.h>
using namespace std;
int main(){
int N, M;
scanf("%d %d", &N, &M);
vector<unsigned long long> V(N), W(N);
for(int i=0;i<N;i++){
scanf("%llu %llu", &V[i], &W[i]);
}
vector<unsigned long long> T(M);
for(int j=0;j<M;j++) scanf("%llu", &T[j]);
const long long MOD = 1000000007;
long long ans = 0;
for(int mask=0; mask<(1<<N); mask++){
// build basis
unsigned long long basis[64];
for(int b=0;b<64;b++) basis[b]=0;
for(int i=0;i<N;i++){
unsigned long long x = (mask>>i&1) ? W[i] : V[i];
for(int b=59;b>=0;b--){
if(!((x>>b)&1ULL)) continue;
if(basis[b]==0){ basis[b]=x; break; }
x ^= basis[b];
}
}
// check all targets
bool ok=true;
for(int j=0;j<M && ok;j++){
unsigned long long x = T[j];
for(int b=59;b>=0;b--){
if(!((x>>b)&1ULL)) continue;
if(basis[b]==0){ ok=false; break; }
x ^= basis[b];
}
if(x!=0) ok=false;
}
if(ok){
ans++;
if(ans>=MOD) ans-=MOD;
}
}
printf("%lld\n", ans%MOD);
return 0;
}
この解説は claude4.8opus-high によって生成されました。
投稿日時:
最終更新: