AtCoder Grand Contest 045 - A. Xor Battle 解法ログ
AGC045 - A. Xor Battle
- 問題 : atcoder.jp
- 公式解説 : https://img.atcoder.jp/agc045/editorial.pdf
- 提出 : Submission #15405606 - AtCoder Grand Contest 045
AtCoder Problemsの青diff埋めをしていたら、手がつけられなくて、解説pdfや解法ブログを読んでも解法の理解に時間がかかったので、解法にたどり着くまでの記録をまとめます。(AGC045のとてもわかりやすい公式Youtube解説放送もない?)
問題概要
ステップの2人制ゲーム。手番と操作内容が予め固定されていて、プレイヤーは操作内容を実行するかしないかを選択できる。
ゲームの盤面は一つの変数のみで、で初期化されている。ゲームのステップ目には手番と操作内容の整数が与えられていて、プレイヤーは変数をに置き換えるか、そのままにするかを選択できる。
プレイヤーはゲーム終了時のをにしたい。プレイヤーはゲーム終了時のを以外にしたい。
勝つのはどちらか。
ケーススタディ
ゲームの必勝に関する考察では、何はともあれゲームの終局付近から考察を進めるのが基本です。*1
パターン1 : 最後の手番がのとき
プレイヤーが勝つ。
最後の手番で、
-
- 操作を行えばプレイヤーの勝ち
-
- 操作を行わなければプレイヤーの勝ち
パターン2 : 最後の手番がのとき
ならプレイヤーが勝つ。
-
- 操作を行えばプレイヤーの勝ち
-
- 操作を行わなければプレイヤーの勝ち
の時は、この時点ではわからない。
パターン3 : 最後の手番がのとき
整数を
非負整数はxorに関してベクトル空間をなし、その構造はと同型です。よって以下では、と見なします。*2
の任意のxor上の線形結合によって得られるベクトルの集合をとする。は上のを含む最小の部分空間であることに注意する。
なら、プレイヤーが勝つ。
-
- 操作を行えばプレイヤーの勝ち
-
- 操作を行わなければプレイヤーの勝ち
なら、さらに遡らないとわからない。が、プレイヤーが操作してもしなくても、その後にプレイヤーはその選択を打ち消すように行動を取れる。つまり、この場合、プレイヤーは操作しないものとしてスキップして良い。
解法
ゲームを後方から見ていく。あるステップ以降でプレイヤーが作れる整数(ベクトル)の集合をとする。を更新していく。
あるステップについて
- のとき
- にを含んだ線形結合で表せるベクトルを追加する。
- のとき
- にが含まれているか調べる。
- 含まれているなら何もしない。
- 含まれていないなら、プレイヤーの勝ちとする。
最初のステップまで到達できた時、プレイヤーはプレイヤーの任意の行動に対して、打ち消しの行動を取れるということなので、プレイヤーの勝ちとする。
XORの基底の管理
実装するためには、
- にを含んだ線形結合で表せるベクトルを追加する。(insert)
- にが含まれているか調べる。(count)
の二つのクエリを高速に動作させるデータ構造が必要になる。解説pdfには
この DP は,xor の基底を管理することで,効率的に計算することができます.*3
とあり、「xor の基底を管理」するデータ構造が必要になる。
基底ベクトル行列を掃き出し法を用いて行簡約階段形で持っておくことで、insertとcountの動作をO(bit数)で行える。
コードを参考に動作を確認して欲しい。
#include <bits/stdc++.h> #define debug(var) do{std::cerr << #var << " : ";view(var);}while(0) template<typename T> void view(T e){std::cerr << e << std::endl;} template<typename T> void view(const std::vector<T>& v){for(const auto& e : v){ std::cerr << e << " "; } std::cerr << std::endl;} template<typename T> void view(const std::vector<std::vector<T> >& vv){ for(const auto& v : vv){ view(v); } } template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; } template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return 1; } return 0; } using namespace std; typedef long long ll; typedef long double ld; typedef vector<ll> v1; typedef vector<v1> v2; typedef vector<v2> v3; typedef unordered_map<ll, unordered_map<ll, ll>> graph; const ll INF = 1ll << 50; const ll mod = 1000000007; class xor_set{ private: v1 w; public: xor_set () {} void insert(ll x){ for(ll v : w) if(v & -v & x) x ^= v; if(x == 0) return; for(ll& v : w) if(x & -x & v) v ^= x; w.push_back(x); } ll count(ll x){ for(ll v : w) if(v & -v & x) x ^= v; if(x == 0) return 1; else return 0; } };
コード中のv & -v
とあるのはlong long
型整数v
の最下位に立っているbitのみを立たせた変数を表現する方法で、Binary Indexed Treeで使われていたりするテクニック。コードの多くはこちら*4を参考に作成した。
感想
むずい。