P5208 笔记

emm,算是来****后听得比较明白的一道题。

思维链有点长,但是居然有人场切了orz。

题意

\(N\) 个物品,每个物品是 \(0\)\(1\),你需要通过向交互库提问猜出每个物品的权值。保证至少有一个物品是 \(1\),且你知道 \(1\) 物品数量的奇偶性。

每次猜测,你需要给出两个集合 \(S, T\),交互库会告诉你 \(S\)\(T\) 中哪个物品权值和大,但如果相等会乱回答。你需要保证询问的 \(|S| + |T|\) 之和 \(\le M\),其中 \(M\) 是一个给定的阈值。

思路

首先 \(\sum S\)\(\sum T\) 相等乱回答可以直接转化为:若回答 \(0\)\(\sum S \ge \sum T\);若回答 \(1\)\(\sum S \le \sum T\)

首先思考一个 \(N^2\) 次询问的方法,我们想要找到哪些是 \(1\) 哪些是 \(0\),那不如将它排序。把任意两个物品的大小关系问出来,小的向大的连单向边,这样整张图是一个竞赛图,缩点之后是一条链。

于是重排这个序列就变成了子任务 3,由于至少有一个 \(1\),故 \(A_N = 1\)。考虑把第一个 \(1\) 在哪里二分出来,二分的过程中,每次询问相邻两个数之和与 \(A_N\)\(0 + 0\) 一定是 \(0\)\(1 + 1\) 一定是 \(1\)。最后只有中间的数 \(0 + 1\) 可能有问题,可以通过奇偶性判断。

接着考虑优化这个算法,尝试减少排序的询问次数,一个想法就是采用归并排序,询问和归并排序的过程完全一致,每次拉出链头问即可。这样询问次数就是 \(2n \log n\),但由于常数的问题和上面的算法得分一致。。。

继续优化,一个观察是,归并排序中链的长度决定了询问次数,如果能缩减这条链,那询问次数应该就降下去了!所以在归并排序中,考虑把链上的数用子任务 3 的方式问出来,对于问出来的数,把它在链上删掉,只留下中间的那个不确定的数。这样每次归并返回的链,大小至多为 \(1\)

然而有个问题是链全部相等就问不出来了(即无法区分全 0 和全 1),所以考虑先用 \(2N\) 的次数问出来最大值 \(v\),由于 \(A_v = 1\),用最大值来问就行了。

接着又发现,好像连归并排序都不需要了!直接对前缀维护一个尚未确定的点 \(s\),让 \(i\)\(1\) 扫到 \(N\),每次询问 \(S = \{i\}, T = \{s\}\)\(S = \{i, s\}, T = \{v\}\),就能确定 \(s, i\) 中的某个点,最后 \(i\) 扫完剩下的 \(s\) 通过奇偶性判断。算一下可以做到 \(7N\) 的询问次数,足以通过前 \(5\) 档!

现在考虑节省到 \(5N\),似乎前面求最大值的部分有点浪费,如果能在扫描 \(i\) 的过程中求出最大值就好了。于是维护 \(v\) 表示前缀最大值,对于 \(i\),分情况讨论:

  • 先询问 \(S = \{i\}, T = \{s\}\) 来确保 \(A_i \le A_s\),如果不满足则交换 \(s, i\)
  • 接着询问 \(S = \{i, s\}, T = \{v\}\)
    • 如果 \(A_i + A_s \le A_v\),说明 \(A_i\) 一定是 \(0\)\(v, s\) 不变。

    • 否则 \(A_i + A_s \ge A_v\),此时有几种情况:

      1. \(A_v = 0\)\(A_i, A_s\) 取任意值。
      2. \(A_v = 1, A_s = 1\)\(A_i\) 取任意值。

      可以发现这个时候 \(A_s\) 一定 \(\ge A_v\),且 \(A_i\) 无法确定。

      由于 \(v\) 是不确定的,所以要先把 \(v\) 放进序列 \(z\)。接着把 \(v\) 赋值为 \(s\)\(s\) 赋值为 \(i\)

这个序列 \(z\) 满足序列内元素单调递增,可用子任务 \(3\) 的方式求出。

于是整个题就做完了!下面是代码:

#include <bits/stdc++.h>

int query(int *S, int nS, int *T, int nT);

int _S[10], _T[10];
int ask(const std::vector<int> &S, const std::vector<int> &T) {
	int nS = S.size();
	for (int i = 0; i < nS; ++i) {
		_S[i] = S[i];
	}
	int nT = T.size();
	for (int i = 0; i < nT; ++i) {
		_T[i] = T[i];
	}
	return query(_S, nS, _T, nT);
}

void find_price(int task_id, int N, int K, int *ans) {
	if (N == 1) {
		ans[0] = 1;
		return ;
	}

	int _t = ask({0}, {1});
	int v = _t, u = !v;
	std::vector<int> z{v};

	if (task_id == 3) {
		for (int i = 2; i < N; ++i) {
			z.push_back(i);
		}
		if (ask({0}, {N - 1})) {
			v = N - 1;
		} else {
			v = 0;
			std::reverse(z.begin(), z.end());
		}
	} else {
		for (int i = 2; i < N; ++i) {
			int a = i, b = u;
			if (ask({b}, {a})) {
				std::swap(a, b);
			}

			if (ask({a, b}, {v})) {
				ans[a] = 0;
				u = b;
			} else {
				z.push_back(b);
				u = a;
				v = b;
			}
		}
	}

	if (z.size() > 1) {
		int lo = 0, hi = (int)z.size() - 2;

		while (lo < hi) {
			int mid = (lo + hi) / 2;
			if (ask({z[mid], z[mid + 1]}, {v})) {
				lo = mid + 1;
			} else {
				hi = mid;
			}
		}

		for (int i = 0; i < lo; ++i) {
			ans[z[i]] = 0;
		}
		for (int i = lo + 1; i < z.size(); ++i) {
			ans[z[i]] = 1;
		}

		int w = z[lo];
		if (ask({w}, {u})) {
			std::swap(u, w);
		}

		if (ask({v}, {w, u})) {
			ans[w] = 1;
		} else {
			ans[u] = 0;
			u = w;
		}
	}

	int cnt = std::count(ans, ans + N, 1);
	ans[u] = (cnt & 1) ^ K;
}
posted @ 2025-08-14 11:28  CTHOOH  阅读(6)  评论(0)    收藏  举报