1 条题解

  • 0
    @ 2026-5-18 20:14:50

    E2. 游戏(困难版)详细题解

    问题重述

    给定一棵以 11 为根的有根树,每个节点 ii 有一个权值 wiw_i。两人轮流操作,Cirno 先手。每轮操作:假设上一轮对手选了节点 jj(第一轮无限制),当前玩家必须选择一个未被删除的节点 ii,满足 wi>wjw_i > w_j,然后删除节点 ii 的整棵子树(包括 ii 本身)。无法操作的玩家获胜。双方都采取最优策略。对于每个测试用例,输出 Cirno 在第一回合可以选择的所有节点(按升序);若不存在则输出 00

    博弈分析

    M=max{wi}M = \max\{w_i\}

    • 若当前玩家选择了一个权值为 MM 的节点,则对手无法操作,对手获胜 → 选权值为 MM 的节点是必败的。
    • 考虑权值为 M1M-1 的节点 uu
      如果 uu 的子树外存在权值为 MM 的节点 vv,那么选 uu 后对手只能选 vv,而选 vv 是必败态,因此 uu 是必胜态。
      如果 uu 的子树内包含了所有权值为 MM 的节点,则选 uu 后所有 MM 节点被删除,对手无合法操作,对手获胜 → uu 是必败态。
    • 依此类推,对于任意节点 ii,定义 S={jwj>wi}S = \{j \mid w_j > w_i\}
      SS 中存在至少一个节点不在 ii 的子树内,则选 ii 后对手必须从 SS 中选择一个节点,而所有 SS 中的节点(因为权值更大)都是必败态,因此对手必败 → ii 是必胜态。
      SS 中所有节点都在 ii 的子树内,则选 iiSS 被全部删除,对手无步可走,对手获胜 → ii 是必败态。

    核心结论:节点 ii 是 Cirno 第一步必胜选择 当且仅当ii 的子树外存在至少一个节点 jj 满足 wj>wiw_j > w_i

    转化为树上区间查询

    对树进行 DFS,得到每个节点的 DFS 序 dfn[i]dfn[i] 和其子树内最大的 DFS 序 low[i]low[i]。节点 ii 的子树对应连续区间 [dfn[i],low[i]][dfn[i], low[i]]。子树外的节点对应两个区间:[1,dfn[i]1][1, dfn[i]-1][low[i]+1,n][low[i]+1, n]

    定义数组 nfd[x]nfd[x] 表示 DFS 序为 xx 的节点编号。预处理:

    • 前缀最大值 $pre[x] = \max\{w_{nfd[1]}, w_{nfd[2]}, \dots, w_{nfd[x]}\}$,并令 pre[0]=0pre[0]=0
    • 后缀最大值 $suf[x] = \max\{w_{nfd[x]}, w_{nfd[x+1]}, \dots, w_{nfd[n]}\}$,并令 suf[n+1]=0suf[n+1]=0

    对于节点 ii,子树外的最大权值为:

    $$out = \max\big(pre[dfn[i]-1],\; suf[low[i]+1]\big) $$

    判断条件为:out>wiout > w_i

    算法步骤

    1. 读入 tt 组测试数据。
    2. 对每组数据:
      a. 读入 nn 和权值 w[1..n]w[1..n]
      b. 建图(无向边)。
      c. 从根 11 开始 DFS,记录 dfndfnlowlownfdnfd 数组。
      d. 计算前缀最大值数组 pre[0..n]pre[0..n]
      e. 计算后缀最大值数组 suf[1..n+1]suf[1..n+1]
      f. 遍历所有节点 ii1in1 \le i \le n):
      计算 out=max(pre[dfn[i]1],suf[low[i]+1])out = \max(pre[dfn[i]-1], suf[low[i]+1])
      如果 out>w[i]out > w[i],则将 ii 加入答案集合。
      g. 将答案集合排序后输出:先输出个数 kk,再按升序输出所有节点。如果答案集合为空,输出 00

    复杂度分析

    • DFS 遍历:O(n)O(n)
    • 预处理前缀/后缀:O(n)O(n)
    • 遍历判断:O(n)O(n)
    • 排序答案:O(klogk)O(k \log k),其中 kk 为答案个数,knk \le n
    • 总复杂度:O(nlogn)O(n \log n),实际 nn 总和不超过 4×1054 \times 10^5,完全可行。
    • 空间复杂度:O(n)O(n)

    代码实现(C++)

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 4e5 + 10;
    vector<int> e[N];
    int w[N], dfn[N], nfd[N], low[N], pre[N], suf[N];
    int n, id;
    
    void dfs(int u, int fa) {
        dfn[u] = ++id;
        nfd[id] = u;
        for (int v : e[u]) {
            if (v == fa) continue;
            dfs(v, u);
        }
        low[u] = id;
    }
    
    void solve() {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            cin >> w[i];
            e[i].clear();
        }
        for (int i = 1; i < n; ++i) {
            int u, v;
            cin >> u >> v;
            e[u].push_back(v);
            e[v].push_back(u);
        }
        id = 0;
        dfs(1, 0);
        pre[0] = 0;
        for (int i = 1; i <= n; ++i) {
            pre[i] = max(pre[i-1], w[nfd[i]]);
        }
        suf[n+1] = 0;
        for (int i = n; i >= 1; --i) {
            suf[i] = max(suf[i+1], w[nfd[i]]);
        }
        vector<int> ans;
        for (int i = 1; i <= n; ++i) {
            int outside = max(pre[dfn[i]-1], suf[low[i]+1]);
            if (outside > w[i]) {
                ans.push_back(i);
            }
        }
        sort(ans.begin(), ans.end());
        if (ans.empty()) {
            cout << "0\n";
        } else {
            cout << ans.size();
            for (int x : ans) cout << ' ' << x;
            cout << '\n';
        }
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        while (T--) solve();
        return 0;
    }
    • 1

    信息

    ID
    7222
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者