题解:P5473 [NOI2019] I 君的探险
复读一下学长的题解。
考虑把题目弱化一下,假设图是一棵树,并且每个点的父亲编号都小于它。可以二分它的父亲所在区间
但是这样做显然操作次数过多,因为只给了你单点翻转。
考虑整体二分,记父亲在
你发现 check 函数还没用过。
考虑怎么推广到一般图上,你发现直接放上去就是对的。只是可能有多个父亲,所以可能会出现偶数个父亲在区间里,导致你会认为它的父亲不在这个区间里。
考虑随机化,每次打乱你考虑的点的顺序,这样就可能被洗成奇数条边,然后 report 一条边后就把它删掉,就是每次翻转的时候把这个点连接的已经 report 的边连接的另一个点翻转了,然后如果询问结果和数组相同说明没被翻转过,然后一整体二分后把所有点 check 一遍,如果记录完了,就把这个点踢出排列。重复上述操作直至记录完所有边。
考虑为啥是对的。一个度数为
注意 1~5 的测试点用上述做法不能通过,只能补个暴力拼上去。原因给个图。
#include <bits/stdc++.h>
using namespace std;
void modify(int x);
int query(int x);
void report(int x, int y);
int check(int x);
mt19937 rnd(time(0));
const int N = 2e5 + 5;
vector<int> g[N];
int cnt, s[N];
inline void solve(int l, int r, vector<int> p, const vector<int>& V) {
if (l == r) {
for (auto i : p) {
g[i].push_back(V[l]), g[V[l]].push_back(i);
report(i, V[l]);
++cnt;
}
return ;
}
int mid = l + r >> 1; vector<int> pl, pr;
for (int i = l; i <= mid; ++i) {
modify(V[i]);
s[V[i]] ^= 1;
for (auto v : g[V[i]]) s[v] ^= 1;
}
for (int i = mid + 1; i <= r; ++i) if (query(V[i]) ^ s[V[i]]) pl.push_back(V[i]);
for (auto i : p) {
if (query(i) ^ s[i]) pl.push_back(i);
else pr.push_back(i);
}
for (int i = l; i <= mid; ++i) {
modify(V[i]);
s[V[i]] ^= 1;
for (auto v : g[V[i]]) s[v] ^= 1;
}
solve(l, mid, pl, V), solve(mid + 1, r, pr, V);
}
void explore1(int N, int M) {
for (int i = 0; i < N - 1; ++i) {
modify(i);
s[i] ^= 1;
for (auto v : g[i]) s[v] ^= 1;
for (int j = i + 1; j < N; ++j) {
if (query(j) ^ s[j]) {
s[j] ^= 1;
report(i, j);
g[i].push_back(j), g[j].push_back(i);
}
}
}
}
void explore2(int N, int M) {
vector<int> V;
for (int i = 0; i < N; ++i) V.push_back(i);
while (1) {
solve(0, V.size() - 1, {}, V);
if (cnt == M) break;
vector<int> t;
for (auto i : V) if (!check(i)) t.push_back(i);
V = t; shuffle(V.begin(), V.end(), rnd);
}
}
void explore(int N, int M) {
if (N <= 500) explore1(N, M);
else explore2(N, M);
}