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\),此时有几种情况:
- \(A_v = 0\),\(A_i, A_s\) 取任意值。
- \(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;
}

浙公网安备 33010602011771号