D. Finding Zero
题意:
这是一道交互题。
有一个由一个 0 0 0 和 n − 1 n-1 n−1 个正整数构成的一个随机数组,按下标记为 a 1 … … a n a_{1}……a_{n} a1……an。现如今有一种询问方式,对于每次询问,可选择三个互不相同的下标 i 、 j 、 k ( 1 ≤ i , j , k ≤ n ) i、j、k(1\leq i,j,k\leq n) i、j、k(1≤i,j,k≤n),然后会返回 m a x ( i , j , k ) − m i n ( i , j , k ) max(i,j,k)-min(i,j,k) max(i,j,k)−min(i,j,k) 的值。
最多 2 ∗ n − 2 2*n-2 2∗n−2 次查询之后,就可以选定两个下标作为 0 0 0 值可能存在的下标,也就是说,必须给出两个不同的下标 x 、 y ( 1 ≤ x , y ≤ n ) x、y(1\leq x,y\leq n) x、y(1≤x,y≤n),如果 a x = = 0 ∣ a y = = 0 a_{x}==0|a_{y}==0 ax==0∣ay==0 的话就赢了。
每个测试用例中的数组是预先固定的,不会改变。
思路:
题目给出的每一个数字都是有深意的,所以第一步应该从查询的方式、猜测答案的方式以及 2 ∗ n − 2 2*n-2 2∗n−2 入手。
最终需要给出两个可能的下标,那就说明题目所给的次数不足以精准的找到结果,或者是查询的方式不可能做得到。再联想查询的方式,那么最有可能的一种情况就是:最终只可能确定出 0 和数组中最大值所在的下标。
那接下来就顺理成章的将问题从 “如何去找 0 所在的下标” 转变为 “如何将 0 与最大值放在同一组(三个数)”。之后就很顺畅的想到,既然是交互题,那就要将询问次数利用最大化,而且大概率题目所给的限制刚刚好就是通解的最差情况。
那我们可以将初始的三个数定为 a 1 、 a 2 、 a 3 a_{1}、a_{2}、a_{3} a1、a2、a3,然后把第三个数拿出来用来和其他数 ( a 4 … … a n ) (a_{4}……a_{n}) (a4……an) 做替换,如果查询的差值增大,那就果断换掉,这样第一轮遍历一共需要进行 n − 2 n-2 n−2 次的查询,确定了第三个数的下标 z z z 。并且我们可以保证,在 a 1 、 a 2 、 a z a_{1}、a_{2}、a_{z} a1、a2、az 三个数中,必然有 0 0 0 或者最大值中的一到两个。
仅仅一个是不够的,我们还需要进行第二次遍历,保证我们手中的这三个数所能查询到的差值应当为最大,即 0 0 0 与最大值都在这三个数中。鉴于第三个数是我们第一次遍历辛辛苦苦“淘”出来的,第二次遍历就拿第二个数与其他书来做替换,规则与第一次遍历类似,这样我们第二次遍历就会用掉 n − 3 n-3 n−3 次的查询,确定了第二个数的下标 y y y,同时可以保证,在 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1、ay、az 三个数中,必然有 0 0 0 和最大值,这时候询问的回答就是最大值的值,也是所能得到答案的最大值。
现在我们还剩下 3 3 3 次询问次数,那不正好嘛!我们只需要从剩余 n − 3 n-3 n−3 个数中随便挑一个,然后依次替换 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1、ay、az,假使替换过后查询的回答没有变,那么说明 0 0 0 和最大值一定就是剩下的那两个数。这里最差情况就是 3 3 3 次,刚刚好用完给定查询次数。
PS:那些 w a 2 、 w a 3 、 w a 4 wa2、wa3、wa4 wa2、wa3、wa4 的同志们应该都是在最后一步被坑了,比如有可能 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1、ay、az 三个数是一个 0 0 0 和两个最大值,或者随便调用来替换的那个数也恰好是最大值,又或者这两种情况都撞上了……为了避免繁杂的情况讨论,上面那种方法还是比较省心的。
时间复杂度: O ( n ) O(n) O(n)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define re register int
#define PII pair<int,int>
#define x first
#define y second
#define cf int _; cin>> _; while(_--)
#define sf(x) scanf("%lld",&x)
#define sf2(x,y) scanf("%lld %lld",&x,&y)
#define pft(x) printf("%lld ",x)
#define pfn(x) printf("%lld\n",x)
#define pp(x,y) printf("%lld %lld\n",x,y)
#define fer(i,a,b) for(re i = a ; i <= b ; ++ i)
#define all(x) (x).begin(),(x).end()
int n;
int x, y, z, ans, tmp;
int xx, yy, zz;
void get() {
printf("? %lld %lld %lld\n", x, y, z);
fflush(stdout);
sf(tmp);
}
void put(int a, int b) {
printf("! %lld %lld\n", a, b);
fflush(stdout);
}
signed main() {
cf{
sf(n);
x = 1, y = 2, z = 3;
xx = x;
get();
ans = tmp;
int j = 3;
for (int i = 4; i <= n; i++) {
z = i;
get();
if (tmp >= ans) {
j = i;
ans = tmp;
}
}
z = j;
zz = z;
j = 2;
for (int i = 3; i <= n; i++)
if (i != zz) {
y = i;
get();
if (tmp >= ans) {
j = i;
ans = tmp;
}
}
y = j;
yy = y;
int id = 1;
while (id == xx || id == yy || id == zz)
id++;
x = id;
get();
if (ans == tmp) {
put(yy, zz);
continue;
} else
x = xx;
y = id;
get();
if (ans == tmp) {
put(xx, zz);
continue;
} else
y = yy;
z = id;
get();
if (ans == tmp) {
put(xx, yy);
continue;
} else
z = zz;
}
return 0;
}