Educational Codeforces Round 73 (Rated for Div. 2) E. GameWithString
問題: https://codeforces.com/contest/1221/problem/E
問題概要
Alice と Bob が2人でゲームをする.先攻は Alice である.
'.' と 'X' のみからなる,$ \left| s \right| (\leq 3 \times 10^5) $ を満たす文字列 s がある.
この文字列 s から, Alice は $ a (\leq 3 \times 10^5) $ 個, Bob は $ b (\leq 3 \times 10^5) $ 個連続している '.' をすべて 'X' に変える操作を行う.どちらも操作ができなくなったとき,最後に操作したプレイヤーの勝利となる.ただし $ a \gt b $ が必ず成り立つ.
2人が最適に操作を選択したとき,Alice が勝利するかどうかを答えよ.
解法概要
参考: keymoon さんのツイート
E、Bだけ取れる要素があったら確実に駄目で(Aが取れる要素がない状態で渡されてもやり過ごして返せるので)、なのでBはやり過ごせる要素を作りたい、Aはつくらせたくないみたいにできる
— keymoon (@kymn_) 2019年9月19日
b<=n<aな要素が一個でもあったら駄目、2b<=nな要素から作り出せるのでそれが2個以上あったら駄目、(つづく)
この条件を満たしている場合は最大の要素以外は全部a<=n<2bなので偶奇で勝敗が決定できるので、Aは初手最大を取るとしてその中で取る要素を全探索(めんどくさいのでその部分はコードを貼ります) pic.twitter.com/2i39IJHWxN
— keymoon (@kymn_) 2019年9月19日
連続する $ v $ 個の '.' を," $ v $ の要素" と呼ぶこととする.
$ a \gt b $ より, Bob が有利なゲームである.なぜならば,Alice が操作ができず Bob は操作ができる要素 ($ b \le v \lt a $ となる $ v $ の要素) が1つでもあるならば,Alice は負けてしまうからである.残りの要素が「2人とも操作できない要素」と「Alice が操作ができず Bob は操作ができる要素」だけになったとき,Alice のターンなら Alice は操作ができずに負けるし, Bob のターンなら Bob が操作した後やはり Alice は操作ができず負ける.
Bob しか操作できない要素がなければ,最適な戦略は Bob は自分だけが操作できる要素を作り出すこと,Alice は Bob だけが操作できる要素を作らせないこととなる. Bob しか操作できない要素を作り出すには,$ 2b \le v $ となるような要素に対して片側を $ b \le i \lt a $ 個だけ残して操作すればよい.
これらの戦略を頑張って場合分けで実装する. 場合分けの詳細はソースコードのコメント参照.
ソースコード
#include <iostream> #include <string> #include <vector> using namespace std; bool solve() { int a, b; cin >> a >> b; string s; cin >> s; if(s.back() != 'X') s += 'X'; int n = s.length(), continuous_cnt = 0; // '.' が何個続くかを格納したベクトル v を作る vector<int> v; for(int i=0;i<n;++i) { if(s[i] == 'X') { if(continuous_cnt >= b) { // b未満の要素は2人とも取れないので考えなくていい v.push_back(continuous_cnt); } continuous_cnt = 0; } else { ++continuous_cnt; } } int cnt = 0; int max_v = 0; for(int vv: v) { if(vv < a) return false; // Bob しか取れない要素 else if(vv < 2*b) ++cnt; // 2人とも取れて,かつ Bob が自分か取れない要素を作り出せない要素 else { // Bob が自分しか取れない要素を作り出せる要素 if(max_v) { return false; } else { max_v = vv; // 1つだけなら Bob しか取れない要素を作られる前に阻止できる } } } // Bob しか取れない要素の作成を阻止してからの戦い // この地点で max_v 以外は (a <= vv && vv < b) な要素だけでできている if(max_v) { /* * Alice は最初のターンで max_v から a 個を取ったものと仮定する * (他の要素から取ると,max_v ( >= 2*b) を用いて Bob しか取れない要素を作り出されてしまうので) */ for(int i=0;i<=max_v-a;++i) { // Alice が最初のターンで取る位置は全探索 int j = max_v - (i+a); // Alice がa個取ると max_v が i 個と j 個に分かれる if((b <= i && i < a) || (b <= j && j < a) || 2*b <= i || 2*b <= j) { // Bob しか取れない要素ができちゃったり, // 次の Bob のターンで Bob しか取れない要素を作られてしまうような取り方はしたくない continue; } else { // (a <= vv && vv < b) な要素 + 最初のターンで取った分 + i から取れる分 + j から取れる分 // の合計が奇数ならそのように取ればいい if((cnt + 1 + ((a <= i)?1:0) + ((a <= j)?1:0)) % 2) return true; } } // 勝てる手筋がなければアウト return false; } // (a <= vv && vv < b) な要素だけなら,単純に要素数の偶奇 return cnt % 2; } int main() { std::ios::sync_with_stdio(false); cin.tie(nullptr); int q; cin >> q; while(q--) { cout << (solve()?"YES":"NO") << endl; } }
所感
Codeforces 紫になりました! これで AtCoder,Codeforces,TopCoder SRM の色がすべて違うことに……
鬼場合分けもそろそろ本番で解けるようにならないとな.