自力で解けたが、コンテスト時間内に間に合わなかった。
割と面白いので解説を書く。
解法
まず、 回で
を特定する。
次に、 の倍数を
まで調べれば良い。
各判定は
回でできるので、全体で
回でできる。
#include "my_template.hpp" using namespace std; void solve() { auto query = [](int l, int x) -> int { printi("?", l, x); INT(r); return r; }; auto answer = [](int m) -> int { printi("!", m); INT(res); return res; }; INT(N, K); int mx = 1; REP(x, 1, N + 1) { int r = query(1, x * N); if (r == N) { mx = x; } } i64 ans = -1; REP(d, 1, N / K + 1) { int val = mx * d; int l = 1, ok = 1; REP(t, K) { if (l == N + 1) { ok = 0; break; } int r = query(l, val); if (r == N + 1) { ok = 0; break; } else { l = r + 1; } } ok &= l == N + 1; if (ok) { ans = val; } } int res = answer(ans); assert(res == 1); return; } int main() { INT(T); REP(T) solve(); return 0; }
考察
以下のような流れで考察したと思う。
は
を固定すると
について単調増加であることに気づく。
答えを決め打てば判定は
でできることに気づく。
まず、部分問題として
の場合を考える。
回で初項を特定すればあとは各項が初項と一致するか判定すれば良い。
の場合でもとりあえず同じように初項を特定してみる。
次に、初項がわかっているときに何ができるか考える。区間幅を
と決め打って、
? 1 2 * A[0]
を投げてみると、が返ってきた場合は
がわかる。
が返ってきた場合は
がわかるが、
の具体的な値はわからない。 しかしこのように
となるのは高々
回で、区間が伸びるのも
回なので、
回で最大値が特定できる。最大値は使い道がありそうとなる。
並行して答えの上界を考える。鳩の巣原理より、長さが
以下であるような区間が必ず存在する。この区間に対する答えは
以下である。 さらに答えは
の倍数になっているはずであり、以上より試すべき値が
通りしかないことに気づく。判定も含めて全体で
回でできる。
以上より
回で答えはわかる。よく考えると最大値を特定するだけなら区間幅
に対してのクエリを行うイメージでやると
回でできる。よって
回になった。
感想
インタラクティブ問題難しすぎる。 典型手法のようなものを全く知らない気がする。