【Codeforces Round #694 (Div. 1) C】Strange Shuffle
题目链接
翻译
每个人初始都有 \(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;
}