Codeforces 585 D「Ticket Game」

Pocket

「お兄ちゃん、聞きたい問題があるんだけど」

「どうした?」

Codeforces #585 (Div.2) の D 問題なんだけど」

「なんでこの問題?」

「ふと思い立ったの。この問題を解こうって」

「ふと思い立つものなの!?」

「まあ、そんなことで見てみたんだけど、分からないんだよね」

「じゃあ、問題を見てみよう」

Ticket Game

問題概要

「問題はこんな感じ」

‘?’ を含む $n$ 桁の数字の列が書かれたチケットがある。$n$ は偶数であり、’?’ の数も偶数である。

前半 $\frac{n}{2}$ 文字の数字の和と後半 $\frac{n}{2}$ 文字の数字の和が等しいとき、それは “幸福のチケット” である。

Monocarp と Bicarp の二人が交互に、好きな場所の ‘?’ に $0$ から $9$ までの整数を入れるゲームをする。

先手である Monocarp は “幸福のチケット” が嫌いなので、最終結果が “幸福のチケット” になることを阻止すれば勝ちである。

後手である Bicarp は “幸福のチケット” が好きなので、最終結果が “幸福のチケット” になれば勝ちである。

両者が最適に行動したときの勝者を出力せよ。

$2\le n\le 2\cdot 10^5$

「ゲーム問題か。1 つ目のサンプルは、? が無いからすでに決着していて、前半の和と後半の和がともに 5 で等しいから後手の勝ちだね」

「うん。2 つ目のサンプルは “??” だけど、先手が入れた数と同じ数をもう一つの ? に入れたら、絶対に後手が勝てるね」

「そうだね。3 つ目と 4 つ目のサンプルはちょっとややこしいね」

最初の考察

「で、どこまで考えた?」

「まず、前半と後半に分けて考えると、同じ側の中では、’?’ の位置は関係ないなと思った。それと、すでに埋まってる数についても、前半と後半それぞれの合計だけ考えればいいよね。どうせ全部足すんだから」

「そうだね」

「それで、前半と後半の差が 0 になるかどうかが大事だから、(前半の和)ー(後半の和)の値を基準に考えようかなと思った」

「良いね。こうすると、考える値が 1 つで済むようになる」

「だけどそれだと、前半の ‘?’ に数字を入れるとこの値が増えて、後半の ‘?’ に入れるとこの値が減ることになるんだよね。操作が 2 種類あるから困っちゃった」

「ふむふむ」

「DP みたいなことをしたくても、(前半の残り ‘?’ 数)と(後半の残り ‘?’ 数)みたいなのを持たないといけない気がして、$O(n^2)$ になっちゃうんだよね。$n$ が $2\cdot 10^2$ まであり得るから TLE だよ」

「なるほど。考えたのはそこまで?」

「うん。もうお手上げだよー」

定番テクニック

「困っている理由は、操作が 2 種類あることだよね」

「うん。前半の ‘?’ に入れるか後半の ‘?’ に入れるかで効果が変わるからね。最後にどっちを残すかも大事だし」

「操作が 2 種類あって困ったときの定番テクニックに “操作を変換して同一視する” というのがある」

「変換して同一視?」

「2 種類の操作をうまく言い換えたりして、1 種類の操作とみなすこと」

「どうやってやるの?」

「(前半の和ー後半の和)の値が、前半の ‘?’ に数字を入れると $+0\sim +9$ になって、後半の ‘?’ に入れると $-9\sim -0$ になる」

「うん」

前半だとプラスで後半だとマイナスだから、効果が変わっちゃうんだよね」

「うん」

「じゃあ、プラスとマイナスで効果が同じになるようにしよう」

「どういうこと?」

「$+x\sim +y$ の範囲と $-y\sim -x$ の範囲が一致するには、どうすればいい?」

「あ、マイナスにすると順序が入れ替わるから $x$ と $y$ の位置が入れ替わってるんだね。これが一致するには…… $x=-y$ ?」

「正解。そうしよう」

「そうしようって、どうやって……」

「考えてみよう」

「うーん…… 前半の ‘?’ に数字を入れると $-x\sim +x$ になるようにするには…… あ! 最初に $4.5$ が入ってるってことにすれば、$0$ を入れると $-4.5$ になるし、 $9$ を入れると $+4.5$ になる!」

「正解! 全ての ‘?’ に最初は $4.5$ が入っていることにして(前半の和ー後半の和)を計算しておけば、前半でも後半でも、’?’ に数字を入れるとその値が $-4.5\sim +4.5$ になる」

「へー、すごいね。天才じゃん」

対称戦略

「ということで、操作は 1 つになった。(前半の和ー後半の和)を $-4.5\sim +4.5$ する操作だ」

「ここからどうするの?」

「考えよう」

「えっと、この値が最終的に $0$ になっていれば後手の勝ちだよね。だから、先手はこの値を 0 から遠ざけようとするんだね。正なら $+4.5$ して、負なら $-4.5$ する」

「そうだね」

「それで、後手は $0$ に近づけようとするから、正なら $-4.5$ して、後手なら $+4.5$ するよね。あれ、これって先手と後手が操作しても値が変わらないじゃん」

「そうなるね」

「ってことは、最初にこの値が $0$ なら後手が勝ちで、そうじゃないなら先手が勝ちってこと?」

「そうなりそうだね。こういう、相手の操作の対称的な操作を繰り返すことを、対称戦略と僕は呼んでいる。そのままだけど」

「聞いたことある! 『円形のテーブルに牛乳瓶の蓋を交互に置いて、蓋どうしが触れたら負け』っていうゲームで、先手がど真ん中に置いて、あとは後手が置いた場所の反対側に置くようにすれば、後手が置けたら先手も置けるから先手必勝、ってやつ!」

「ちょっと違う気もするけど、そういうことなのかな? まあいいや。これをちゃんと証明してみよう」

必勝法の証明

「証明ってどうやってやるの?」

「いくつか方法はあるけど、2 通りやってみよう。まず、素直な証明から。(前半の和ー後半の和)が $0$ なら後手が勝つということを証明してみよう」

「うーん…… 先手の操作での変化をちょうどプラマイで打ち消す操作をすれば、この値が $0$ のまま ‘?’ の数が減るから、これを繰り返せば後手が絶対に勝つね」

「そうだね。これは ‘?’ の数が偶数だから成り立つことだ。また、操作は $-4.5\sim +4.5$ だから、必ず打ち消す操作が存在するね」

「そこまで確認しないと正しい証明にならないんだね」

「次は、この値が $0$ 以外なら先手が勝つという証明」

「この値が正なら、先手が $+4.5$ すれば、後手がどうやっても元より $0$ に近づくことは無いよね。最小でも $-4.5$ で元に戻すだけだから」

「そうだね。負の場合も、$-4.5$ として同様だね」

「だから、後手は絶対に $0$ にはできない」

「OK。これで証明終わり」

「これだけ? 簡単だね」

「じゃあ、もう一つの証明をやってみよう」

後ろから考える

「もう一つの証明ってどんなの?」

「DP っぽい考え方。ゲーム問題の定番テクニックとして、”後ろから考える” というものがある」

「後ろから?」

「決着した状態から、一手ずつ後退していく。後退しながら、”ここから勝てるか” とか “ここから勝つ条件” とかを計算していく」

「うーん、よくわかんない」

「今回は、“(前半ー後半)の値がどの範囲にあればここから勝てるか” を見ていこう」

“(前半ー後半)の値がどの範囲にあればここから勝てるか” か。最後の後手の手番だと、$-4.5\sim 4.5$ の範囲にあれば勝てるよね」

「うん。その範囲にあれば、かならず $0$ にできるね。その前の先手番では?」

「えっと、先手は $-4.5\sim 4.5$ の範囲に入れたら負けるから、ここから脱出したいんだよね。でもこれ、ほとんどの場合で脱出できるよね?」

「そうだね。脱出できないのはどんな場合?」

「 0 のときだね。先手番の時点で、(前半ー後半)が $0$ だったら、どうあがいても $-4.5\sim +4.5$ の範囲に入っちゃうね」

「そうだね。だから、$0$ のときのみ後手勝ち、それ以外だと先手勝ち」

「じゃあ、その前の後手番は、この値を $0$ にすれば勝ちだから…… って、さっきと同じじゃん!」

「そう。勝つための条件がずっと一緒。先手番なら $0$ であること、後手番なら $-4.5\sim +4.5$ の範囲に入っていること」

「これが永遠に続くんなら、DP で逐次計算する必要無いね」

「そういうことで、最初の状態で(前半ー後半)が $0$ なら後手勝ち、そうでないなら先手勝ち」

「当たり前だけど、同じ結果になったね」

「じゃあ実装しようか」

実装・提出

「実装は簡単だね。数字列には ‘?’ がある場合があるから string で受け取って、前半と後半で処理を分けるんだよね。で、(前半ー後半)が $0$ かどうかで判定。これでいいかな?」

「あ、double の一致判定を == でやるのはダメなんだっけ、double だと誤差もあるからもっと慎重にしないとダメか」

「そうだね。でも実は、今回に限っては大丈夫なんだ」

「どうして?」

「今回、double を使っている理由は、$4.5$ という小数があるからだよね。でも、小数は $4.5$ しか無い」

「うん」

「doulbe では、数が 2 進数で表現されているんだけど、それは(整数$\times 2^e$)というような形で表現されているんだ。だから、2 の整数乗である $0.5$ や $0.25$ などは正確に表せるんだ」

「ややこしいー」

「つまり、$4.5$ は $9\times 2^{-1}$ として表現されているということ。だから、2 の整数乗で表現できない $0.1$ などとは違い、今回の桁数程度では丸め誤差が発生せず厳密な値を持てるんだ」

「よくわかんないけど、それに頼ったらダメな気がするから、安全にいくよ」

「まあ、それが良いね。もっと誤差を恐れるなら、2 倍して全て整数で計算するというのもあるけど、今回はそこまでしなくて大丈夫」

「差が四捨五入して 0 になればいいから、差の絶対値が 0.5 未満なら OK ってことにすればいいかな。これでいいはず」

「これで提出して…… やった、AC!」

「おめでとう。ちなみに sum == 0 で判定したものも AC になるよ。おすすめはしないけどね」

「変なことはしない!」

「終わってみれば簡単な問題だった気もするけど、定番テクニックが詰まった良い問題だね」

「そうだね。今回はあまり意識しなくても大丈夫だったけど、後ろから考えないとダメなゲーム問題もあるので、そっちの考え方も理解しておいてね」

「はーい」

Pocket

スポンサーリンク
レクタングル大
レクタングル大

シェアする

  • このエントリーをはてなブックマークに追加

フォローする