CF2219B2 Unique Values (Hard version) 题解

题目传送门

思路

下文中钦定 \(x\) 为出现了三次的那个数。

首先,我们会发现:如果序列中每一个数都只出现了两次,则一个前缀长度的奇偶性和这个前缀的答案的奇偶性相等。因为每次加入一个数时,答案只会加一(第一次出现)或者减一(第二次出现)。

查询次数 \(33\) 次,想到 \(\log\) 的做法。考虑二分。我们会发现,如果当前查询到一个前缀,满足这个前缀长度的奇偶性和这个前缀的答案的奇偶性不相等,就说明在这个前缀中,一定出现了三次 \(x\)。二分就可以得到最后一个 \(x\) 的位置。考虑继续二分。这次从最后一个 \(x\) 的位置向前二分。查询时加上最后一个 \(x\) 的位置,就可以得到第二个 \(x\) 的位置。同理可得最前面 \(x\) 的位置。

查询次数 \(3 \cdot \log(2n + 1) \le 33\),可以通过此题。

代码

#include <bits/stdc++.h>
using namespace std;

int t, n, resx, resy, resz;

int query(int x, int numx, int numy)
{
	vector<int> num;
	for (int i = 1; i <= x; i++)
		num.push_back(i);
	if (numx != -1) num.push_back(numx);
	if (numy != -1) num.push_back(numy);
	printf("? %d ", (int)num.size());
	for (int i : num) printf("%d ", i);
	printf("\n");
	fflush(stdout); 
	int v;
	scanf("%d", &v);
	return v;
}

void solve()
{
	scanf("%d", &n);
	int l = 1, r = 2 * n + 1;
	resx = resy = resz = 0;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
		if ((query(mid, -1, -1) & 1) ^ (mid & 1)) resx = mid, r = mid - 1;
		else l = mid + 1;
	}
	l = 1, r = resx;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
		if ((query(mid, resx, -1) & 1) ^ ((mid + 1) & 1)) resy = mid, r = mid - 1; // 这里 mid + 1 是因为多了一个数
		else l = mid + 1;
	}
	l = 1, r = resy;
	while (l <= r)
	{
		int mid = (l + r) >> 1;
		if ((query(mid, resx, resy) & 1) ^ (mid & 1)) resz = mid, r = mid - 1; // 这里不用加是因为多了两个数,奇偶性不变
		else l = mid + 1;
	}
	printf("! %d %d %d\n", resz, resy, resx);
	fflush(stdout);
}

int main()
{
	scanf("%d", &t);
	while (t--) solve();
	return 0;
}
posted @ 2026-04-16 16:57  lucasincyber  阅读(32)  评论(0)    收藏  举报