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;
}
posted @ 2025-07-03 17:15  徐子洋  阅读(8)  评论(0)    收藏  举报