题目链接
翻译
每个人初始都有 (k) 张牌,每个单位时间,他们会对自己手上的牌进行如下操作:
每个人都把自己一半的下取整到左边,上取整到右边相邻的一个人。
但是有一个人搞特殊,他不会给左边相邻的人,而是将自己全部的牌都给右边相邻的人。
每过一个单位时间,你都可以询问某个位置上的人有多少张牌,问你如何确定询问及位置,以找到那个特殊的人。
询问的次数不能超过一千次。
题解
模拟一下会发现,在开始的 (frac{n}{2}) 个单位时间内,牌的个数大于 (k) 的人数都会增加。
且这个大于 (k) 的连续段的前一个位置就是所求的特殊位置。如下图:
最中间的 (4) 就是特殊位置。往右 (frac{n}{2}) 个位置可以看到最终都是大于 (k) 的。
因此我们可以这样,先让时间过去 (sqrt{n}) 个单位时间,使得整个序列中会出现一个长度为 (sqrt{n}) 的
连续段,这一段中的数字都是大于 (k) 的。
所以,此时我们从 (i=1) 开始递增,每次增加 (sqrt{n}) ,这样我们肯定能够 (check) 到一个长度为 (sqrt{n})
的区间里的数字,假设这个数字的位置是 (pos),也即 (a[pos]>k), 那么我们就可以让 (pos) 的值一直递减,直到
找到第一个位置 (a[pos]=k),这个 (pos) 就是我们所求的特殊位置。
可能会运气不好, (i=1) 开始递增 (sqrt{n}) 的时候,遇到的数字恰好是 (a[pos]=k) 的,这样会跳过对应的大于 (k) 的连续段,对应测试数据 (test 70), 别问我是怎么知道的:(。
所以还要从 (i=2) 开始 (check) 一下,这个时候,我们不用担心 (3) 次 (sqrt{n}) 加上找 (a[i]=k) 会超过限制。
因为一旦找到了 (i=2) 开始的 (a[pos]>k) 的话,那么 (a[pos-1]) 一定就是等于 (k) 的了,不会需要更多的 (check)。
代码
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 3e5;
int n, k;
int query(int x){
cout << "? " << x << endl;
cout.flush();
cin >> x;
return x;
}
int main() {
#ifdef LOCAL_DEFINE
//freopen("in.txt", "r", stdin);
#endif
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> k;
int sq = sqrt(n);
for (int i = 1;i <= sq; i++){
query(1);
}
for (int j = 1;j <= n; j+=sq){
if (query(j)>k){
int l = j - 1;
if (l <= 0) {
l = n;
}
while (query(l)>k){
l = l - 1;
if (l == 0){
l = n;
}
}
cout << "! " << l << endl;
return 0;
}
}
for (int j = 2;j <= n; j+=sq){
if (query(j)>k){
int l = j - 1;
if (l <= 0) {
l = n;
}
while (query(l)>k){
l = l - 1;
if (l == 0){
l = n;
}
}
cout << "! " << l << endl;
return 0;
}
}
return 0;
}