Codeforces Round #685 Div.2-E1/E2 - Bitwise Queries 解説
数列を当てる系のインタラクティブとても好きだし得意。この前の JOI の数当てもそうだったけど、こういう問題を考えるのがとても楽しいと思えます。
問題リンク
E1 (Easy Version): https://codeforces.com/contest/1451/problem/E1
E2 (Hard Version): https://codeforces.com/contest/1451/problem/E2
問題概要
要素からなる数列 を当てるインタラクティブ問題。解答者はプログラムの中で以下の質問をすることができます。
AND i j
: と の AND を質問する。OR i j
: と の OR を質問する。XOR i j
: と の XOR を質問する。
規定の回数以内の質問で元の数列を当ててください。
制約
は の冪である
Easy: 質問の回数は 回以内
Hard: 質問の回数は 回以内
考察・解法
以下数列は -indexed で表します。また、XOR を 、AND を 、OR を で表します。
とりあえず制約の の冪ということは忘れておいて、数列の全ての数をどうしたら求められるか考えます。
が求まっていなくてもいいので を求めると、後で が求まった際に から を復元することができるので都合がいいです。ということで、全体の方針としては
- を満たす全ての について、
XOR 0 i
を質問する。 - どうにかして を求める (Easy なら 3 回以内、Hard なら 2 回以内)。
とすればいいでしょう。
問題は を求めるパートです。XOR ではどう頑張っても特定できそうには無いことが容易に分かるので、OR か AND を上手く使ってみることにします。
ここで、 の数列の各要素を としておくと、 は の bit だけを反転させただけであり、相対的には同じ(すなわち、 が bit 毎に成り立つということ)であることに気付きます。ここで に現れる値に重複があるかどうかで場合分けをすることができると推測します1。
に重複した値が存在するとき
重複した値を とします。このとき は同じ値であるため、 です。これより、AND i j
もしくはOR i j
により値が具体的に求まったため、 として を求めることができます。 よって、質問は計 回のみで答えを得られます。に重複した値が存在しないとき
制約より、 及び は ] の値を取ります。また、 は の冪であることより、 と の値は を全て取ることが分かります。
ここで、 を適当な値にして実験をしてみます。 を見てみることにしましょう。
例えば とし、 を満たす で上の値を求めてみます。
これを見ると、 のうち である bit は の対応する bit に関わらず 、 である bit は の bit が反映されることが分かります。 の bit が だと XOR の反転によりかならず の bit が逆になるためですね。より厳密に表すと、bitwise NOT を で表せば
であるということです。
は既に知っていて、 以外は のどれかの値を取ることは分かっています。そこで、 となるペア(例えば、)を持ってくると、
となり、無事に が求まりました。質問は 2 回のみなので、計 回で答えを得られます。
実装
をそのまま持つのと の値で逆引きするのとを併用したらいいと思います。あとは上に書いてあることを実装するだけです。
ちなみに、質問をしてその結果を返すメソッドを作ってみたらとても便利でデバッグもしやすかった(特にジャッジにあたる部分を自分で実装する際に切り分けられるのが便利)のでおすすめです。
実装を展開する
public static void Solve(Scanner cin) { var n = cin.ReadInt(); var table = new Set<int>[n]; var axor = new int[n]; for (int i = 0; i < n; i++) { table[i] = new Set<int>(); } table[0].Add(0); int two = -1; for (int i = 1; i < n; i++) { var ret = Ask("XOR", 0, i, cin); if (ret == -1) return; table[ret].Add(i); axor[i] = ret; if (table[ret].Count > 1) two = ret; } if(two != -1) { var and = Ask("AND", table[two][0], table[two][1], cin); if (and == -1) return; var ans = new int[n]; ans[0] = two ^ and; for (int i = 1; i < n; i++) { ans[i] = ans[0] ^ axor[i]; } Console.WriteLine($"! {ans.Join(" ")}"); } else { var one = Ask("AND", 0, table[1][0], cin); var notone = Ask("AND", 0, table[(n - 1) ^ 1][0], cin); var ans = new int[n]; ans[0] = one ^ notone; for (int i = 1; i < n; i++) { ans[i] = ans[0] ^ axor[i]; } Console.WriteLine($"! {ans.Join(" ")}"); } } static int Ask(string q, int i, int j, Scanner cin) { Console.WriteLine($"{q} {i + 1} {j + 1}"); Console.Out.Flush(); var ret = cin.ReadInt(); return ret; }
ACコード: https://codeforces.com/contest/1451/submission/99183661
感想
数当てインタラクティブしか勝たんになりました。楽しかったです。
あと、この問題のおかげで薄橙になったので嬉しいです2。
追記: CF-predictor がバグっていたようで、色変には届きませんでした。
Wow! Coder fancy_lettuce competed in Codeforces Round #685 (Div. 2) and gained +81 rating points taking place 122 https://t.co/k3xxYcrkhY
— 問題をちゃんと見る (@fairly_lettuce) 2020年11月21日
??????????????????????????????????????
なんでPredictorよりも100以上レート変動が小さいんですか???? pic.twitter.com/KUjCbyjSG5
100以上じゃない、70程度か
— 問題をちゃんと見る (@fairly_lettuce) 2020年11月21日
おこです。
-
私は色々な を作っては がどうなるかを眺めていると、 に同じ要素があるときは で検出できるし、しかも は AND によって具体的に求められることに気付きました。しかし、制約を見ると順列になる場合は上手く行かなさそうだったので場合分けを考える、という思考になりました。↩
-
色変記事はアドベントカレンダーの際にまとめて書きます。↩