G - Stone XOR Editorial
by
mechanicalpenciI
操作を繰り返した後の状態において、元々どの袋に入っていた石が同じ袋に入っているか考えることによって、 この問題の答えは次の値と同じであることがわかります。
「袋 \(1\), 袋 \(2\), \(\ldots\), 袋 \(N\) をいくつかのグループに分け、 グループごとにその袋に入った石の数を合計し、 最後にグループごとの石の合計数の排他的論理和をとった値」 としてあり得るものの個数
\(N\) 個のものをグループに分ける方法(グループの順番、グループ内の順番は区別しない)の個数はベル数 \(B(N)\) としてしられています。 ベル数は \(N\) とともに増加し、\(N=12\) においては \( B(12)=4213597(\sim 4\times 10^6)\) 通りとなるため、今回の制約 \((2\leq N\leq 12)\) の下では、全列挙することが可能です。
全列挙する方法はいくつかありますが、例えば次のように深さ優先探索を用いて行うことができます。
深さ \(1\) 、グループ数が \(0\) の状態から始めて次の操作を行います。
その時点における深さが \(d\) 、グループの数が \(k\) であるとき、\(1\leq i\leq k+1\) について、次の行動を順に行う。
- 袋 \(d\) をグループ \(i\) に所属させる。
ただし、\(i=k+1\) のときは新しく袋 \(d\) のみが所属するグループを作ることに対応する。- \(d=N\) ならばその状態における「各グループに属する袋の石の(グループごとの)合計数の排他的論理和」を記録する。
- \(d<N\) ならば次の深さ(すなわち深さ \((d+1)\) )に移動する。
\(i=k+1\) ならばグループ数が増えていることに注意する。すべての \(i\) について探索した後、\(1\) つ前の深さ(すなわち深さ \((d-1)\) )に戻る。
\(d=1\) ならば探索を終了する。
探索が終了した後に記録された数字の種類数が求めるものとなります。
これは非順序集合を管理するデータ構造 (c++ における std::unordered_set
等) などによって、平均計算量 \(O(1)\) で行うことができます。よって、計算量は \(O(NB(N))\)、あるいは探索の道中で排他的論理和を更新していくことによって、\(O(B(N))\) で計算することができます。
これは十分高速であり、よってこの問題を解くことができました。
なお、配列に記録し、後でソートをして求めるというような解法では \(O(B(N)\log B(N))\) の計算量がかかってしまいますが、定数倍が軽い方法であれば、こういった方法でも時間制限をみたすこともあります。
c++ による実装例:
#include <bits/stdc++.h>
using namespace std;
int n;
long long a[12];
long long s[12];
long long val;
unordered_set<long long>st;
void dfs(int idx,int sz){
for(int i=0;i<=sz;i++){
val^=s[i];
s[i]+=a[idx];
val^=s[i];
if(idx==n-1)st.insert(val);
else if(i<sz)dfs(idx+1,sz);
else dfs(idx+1,sz+1);
val^=s[i];
s[i]-=a[idx];
val^=s[i];
}
return;
}
int main() {
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i];
}
for(int i=0;i<n;i++)s[i]=0;
val=0;
dfs(0,0);
cout<<((int)(st.size()))<<endl;
return 0;
}
posted:
last update: