CF2062E2 The Game (Hard Version)
为了叙述方便,下文使用“小C”和“小D”来代指原题面中的 Cirno 和 Daiyousei,分别是游戏中的先手和后手。
Part1:Eazy Version
-
题目要求不能操作的人获胜,意味着每个人都希望让对方操作的是最后一步。
-
可以先观察边界局面,小C何时能“一步杀”取胜。
按照 \(w_u\) 从大到小考虑。我们发现,满足“子树外存在 \(w_x>w_u\)”的第一个 \(u\),就是能“一步杀”的。
显然,假若有“一步杀”点,一定可以作为答案,即小C必胜。
-
没有的情况怎么办?假若这样,小C无论怎么选,小D都无法操作下一步。故小C必败。
上述过程容易利用 \(\text{DFN}\) 序维护做到 \(O(n)\)。
Part2:Hard Version
-
相比于 Eazy Version,我们得求出所有的 \(u\)。
-
依旧按 \(w_u\) 从大到小考虑。
首先,不满足“子树外存在 \(w_v>w_u\)”的 \(u\),显然还是不合法的。
-
考虑小C操作的第一步不能“一步杀”的点。在操作了 \(u\) 之后,这个问题转化为了小D先手的 Eazy Version。
-
所以现在判定 \(u\) 的条件就变成了,能否不被小D“一步杀”。
具体的,对于每个 \(u\),记子树外满足 \(w_x>w_u\) 的所有 \(x\) 的集合为 \(g(u)\)(关于 \(x\) 更具体的意义,详见 E1)。
那么不能被小D”一步杀“当且仅当——任意 \(v(w_v>w_u)\) 节点都满足下列之一:
- \(v\) 在 \(u\) 的子树内(会被删掉)。
- \(u\) 的子树包含了 \(g(v)\) 中的所有点
上述条件的判定,容易用 std::set
与线段树维护做到 \(O(n\log n)\)。
Part3:Code
点击查看代码
#include <bits/stdc++.h>
#define FL(i, a, b) for (int i = (a); i <= (b); ++i)
#define FR(i, a, b) for (int i = (a); i >= (b); --i)
#define fi first
#define se second
using namespace std;
typedef pair<int, int> P;
const int N = 4e5 + 10;
const int INF = 1e9;
int n, w[N];
int tim, L[N], R[N];
vector<int> vc[N], G[N];
P operator + (P a, P b) {
const auto &[aL, aR] = a;
const auto &[bL, bR] = b;
return {min(aL, bL), max(aR, bR)};
}
struct SGT {
P t[N << 2];
void PushUp(int p) {
t[p] = t[p << 1] + t[p << 1 | 1];
}
void Build(int p, int l, int r) {
t[p] = {INF, -INF};
if (l == r) return;
int mid = (l + r) >> 1;
Build(p << 1, l, mid);
Build(p << 1 | 1, mid + 1, r);
}
void Update(int p, int l, int r, int x, int v) {
if (l == r) {
auto &[tL, tR] = t[p];
tL = min(tL, v);
tR = max(tR, v);
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
Update(p << 1, l, mid, x, v);
} else {
Update(p << 1 | 1, mid + 1, r, x, v);
}
PushUp(p);
}
P Query(int p, int l, int r, int L, int R) {
if (L > R)
return {INF, -INF};
if (L <= l && r <= R)
return t[p];
int mid = (l + r) >> 1;
P ret = {INF, -INF};
if (L <= mid) {
ret = ret + Query(p << 1, l, mid, L, R);
}
if (mid < R) {
ret = ret + Query(p << 1 | 1, mid + 1, r, L, R);
}
return ret;
}
} sgt;
void Dfs(int u, int fa) {
L[u] = ++tim;
for (int v: G[u])
if (v != fa)
Dfs(v, u);
R[u] = tim;
}
void Clear() {
tim = 0;
FL(i, 1, n) {
G[i].clear();
vc[i].clear();
}
}
void Solve() {
scanf("%d", &n);
FL(i, 1, n) {
scanf("%d", &w[i]);
vc[w[i]].emplace_back(i);
}
FL(i, 1, n - 1) {
int u, v;
scanf("%d %d", &u, &v);
G[u].emplace_back(v);
G[v].emplace_back(u);
}
Dfs(1, 0);
set<int> s;
vector<int> ans;
sgt.Build(1, 1, n);
FR(i, n, 1) {
if (!s.empty()) {
for (int u: vc[i]) {
int sL = *s.begin();
int sR = *--s.end();
if (L[u] <= sL && sR <= R[u])
continue;
auto [xL, xR] = sgt.Query(1, 1, n, 1, L[u] - 1);
auto [yL, yR] = sgt.Query(1, 1, n, R[u] + 1, n);
if (L[u] <= min(xL, yL) && max(xR, yR) <= R[u]) {
ans.emplace_back(u);
}
}
for (int u: vc[i]) { // calc g(u)
auto sL = *s.begin();
auto sR = *--s.end();
if (L[u] <= sL && sR <= R[u])
continue;
if (L[u] <= sL)
sL = *s.upper_bound(R[u]);
if (R[u] >= sR)
sR = *--s.lower_bound(L[u]);
sgt.Update(1, 1, n, L[u], sL);
sgt.Update(1, 1, n, L[u], sR);
}
}
for (int u: vc[i]) {
s.emplace(L[u]);
}
}
sort(ans.begin(), ans.end());
printf("%d ", ans.size());
for (int x: ans) {
printf("%d ", x);
}
printf("\n");
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
Solve();
Clear();
}
return 0;
}