基于观察可以发现:
- (forall l, r, r - l + 1 < l) 如果查询 (l \% a, r \% a) 返回 (r) 则 (a) 一定不在 ([l, r]) 中。
那么直接地一个想法就是利用二分去查找 (a),每次查询 ([lfloor frac{Mid + 1}{2} floor, Mid]) 这段区间内是否存在 (a)。(基于这个区间是满足上面的观察的)
但是你会发现这样是不行的,因为我们并不知道 (a) 是否在这个区间的左边。
因此,我们需要想办法排除掉 (a) 在该区间左边的情况。
不难发现我们只能通过从做左边开始依次查询最靠左的区间才能解决这个问题,但是一个个位置地查显然是不可以的。
但是回过头来可以发现,我们只需要每次二分的时候 ([1, lfloor frac{Mid + 1}{2} floor)) 内不存在 (a) 即可。
即 (lfloor frac{Mid + 1}{2} floor) 不大于当已知存在区间 ([l, r]) 的 (l)。
那么可知 (lfloor frac{Mid + 1}{2} floor le l ightarrow Mid le 2l ightarrow Mid_{max} = r le 2l)
所以我们最开始确定的区间必须满足 (r le 2l),最优的时候直接卡着上界查询即可。
不难发现此时本质上就是倍增,查询次数最多是 (log n = 30) 的。
最后,因为二分的时候区间长度最大为 (log n - 1),因此总查询次数不大于 (30 + 30 - 1 < 60) 可以通过本题。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 15;
char s[N]; int l, r;
void query(int x, int y) {
printf("? %d %d
", x, y), fflush(stdout);
scanf("%s", s + 1);
}
int main() {
while (true) {
scanf("%s", s + 1);
if(s[1] == 'e') break;
query(1, 2);
if(s[1] == 'y') {
rep(i, 1, 29) {
query((1 << i), (1 << (i + 1)));
if(s[1] == 'x') { l = (1 << i), r = (1 << (i + 1)); break;}
}
while (l + 1 < r) {
int Mid = (l + r) / 2;
query((Mid + 1) / 2, Mid);
if(s[1] == 'x') r = Mid;
else l = Mid;
}
printf("! %d
", r), fflush(stdout);
}
else {
query(2, 1);
if(s[1] == 'x') puts("! 1"), fflush(stdout);
else puts("! 2"), fflush(stdout);
}
}
return 0;
}
本题通过所需确定了最小的查询次数,做交互题的时候一定要有这种意识。