Codeforces Round #770 (Div. 2) D. Finding Zero题解

D. Finding Zero


题意:

这是一道交互题。

有一个由一个 0 0 0 n − 1 n-1 n1 个正整数构成的一个随机数组,按下标记为 a 1 … … a n a_{1}……a_{n} a1an。现如今有一种询问方式,对于每次询问,可选择三个互不相同的下标 i 、 j 、 k ( 1 ≤ i , j , k ≤ n ) i、j、k(1\leq i,j,k\leq n) ijk(1i,j,kn),然后会返回 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 2n2 次查询之后,就可以选定两个下标作为 0 0 0 值可能存在的下标,也就是说,必须给出两个不同的下标 x 、 y ( 1 ≤ x , y ≤ n ) x、y(1\leq x,y\leq n) xy(1x,yn),如果 a x = = 0 ∣ a y = = 0 a_{x}==0|a_{y}==0 ax==0ay==0 的话就赢了。

每个测试用例中的数组是预先固定的,不会改变。

思路:

题目给出的每一个数字都是有深意的,所以第一步应该从查询的方式、猜测答案的方式以及 2 ∗ n − 2 2*n-2 2n2 入手。

最终需要给出两个可能的下标,那就说明题目所给的次数不足以精准的找到结果,或者是查询的方式不可能做得到。再联想查询的方式,那么最有可能的一种情况就是:最终只可能确定出 0 和数组中最大值所在的下标

那接下来就顺理成章的将问题从 “如何去找 0 所在的下标” 转变为 “如何将 0 与最大值放在同一组(三个数)”。之后就很顺畅的想到,既然是交互题,那就要将询问次数利用最大化,而且大概率题目所给的限制刚刚好就是通解的最差情况。

那我们可以将初始的三个数定为 a 1 、 a 2 、 a 3 a_{1}、a_{2}、a_{3} a1a2a3,然后把第三个数拿出来用来和其他数 ( a 4 … … a n ) (a_{4}……a_{n}) (a4an) 做替换,如果查询的差值增大,那就果断换掉,这样第一轮遍历一共需要进行 n − 2 n-2 n2 次的查询,确定了第三个数的下标 z z z 。并且我们可以保证,在 a 1 、 a 2 、 a z a_{1}、a_{2}、a_{z} a1a2az 三个数中,必然有 0 0 0 或者最大值中的一到两个。

仅仅一个是不够的,我们还需要进行第二次遍历,保证我们手中的这三个数所能查询到的差值应当为最大,即 0 0 0 与最大值都在这三个数中。鉴于第三个数是我们第一次遍历辛辛苦苦“淘”出来的,第二次遍历就拿第二个数与其他书来做替换,规则与第一次遍历类似,这样我们第二次遍历就会用掉 n − 3 n-3 n3 次的查询,确定了第二个数的下标 y y y,同时可以保证,在 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1ayaz 三个数中,必然有 0 0 0 和最大值,这时候询问的回答就是最大值的值,也是所能得到答案的最大值。

现在我们还剩下 3 3 3 次询问次数,那不正好嘛!我们只需要从剩余 n − 3 n-3 n3 个数中随便挑一个,然后依次替换 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1ayaz,假使替换过后查询的回答没有变,那么说明 0 0 0 和最大值一定就是剩下的那两个数。这里最差情况就是 3 3 3 次,刚刚好用完给定查询次数。

PS:那些 w a 2 、 w a 3 、 w a 4 wa2、wa3、wa4 wa2wa3wa4 的同志们应该都是在最后一步被坑了,比如有可能 a 1 、 a y 、 a z a_{1}、a_{y}、a_{z} a1ayaz 三个数是一个 0 0 0 和两个最大值,或者随便调用来替换的那个数也恰好是最大值,又或者这两种情况都撞上了……为了避免繁杂的情况讨论,上面那种方法还是比较省心的。

时间复杂度: O ( n ) O(n) On

#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;
}
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值