P2972:Rocks and Trees G ← 树上 Nim 博弈 + SG函数

​【题目来源】
https://www.luogu.com.cn/problem/P2972

【题目描述】
两个人在一棵有根树上玩 Nim 阶梯游戏。
给出一棵有 N 个结点的有根树(节点 1 为根),每个结点有两个属性 Pi 和 Ri,Pi 表示结点 i 的父亲结点,Ri 表示结点 i 的石头数(结点 1 没有石头)。
游戏在两个玩家之间轮流进行,Ted 先手。在每一轮,这轮的玩家可以选择一个非根结点,并且把最多 L 个石头从这个结点向树根靠近一个单位(也就是说,把这些石头移动到它的父结点处)。并且这个玩家至少需要移动一个石子。
当某个玩家没有办法移动石子的时候(也就是所有的石子都移动到结点 1),游戏结束,这个玩家失败。
Ted 将会对布局进行 T 次修改。请帮助他确定,在每步修改之后,以这个布局开局,在双方都用最优策略的前提下他是否能赢得这个游戏。
Ted 的每次修改由两个数字 x 和 y 描述,表示 Ted 将会把结点 x 的石头数修改为 y(注意这是一个“设定”操作,既不是“减少”也不是“增加”)。并且询问修改后谁会获胜。这些修改会累积保持,也就是若往后的操作结点 x 的石头数没有修改,则结点 x 的石头数会保持在 y个。

【输入格式】
第 1 行:三个空格分隔的整数 N、T 和 L。
第 2~N 行:每行包含两个空格分隔的整数 Pi (1≤Pi<i)和 Ri(1≤Ri≤1000)。
接下来 T 行:每行两个整数 Aj(1<Aj≤N) 及 Bj(1≤Bj≤1000),表示 Ted 的每次操作。

【输出格式】
输出 T 行。如果在第 i 次修改后,Ted 可以获胜,那么第 i 行输出 Yes,否则输出 No。

【输入样例】
3 2 10 
1 5 
1 3 
2 3 
3 1​​​​​​​

【输出样例】
No 
Yes

【数据范围】
2≤N≤10^4,1≤T≤10^4,1≤L≤10^3​​​​​​​

【算法分析】
● SG 函数(Sprague-Grundy)
(1)SG 函数的核心是为每个游戏状态计算一个SG 值。其计算依赖于该状态所有后继状态 SG 值的集合,并取该集合的 mex 值(即不在集合中的最小非负整数)作为当前状态的 SG 值。(显然,SG 值一定是非负整数。)
(2)据上所述,可知对于一个游戏状态 u,其 SG 值 SG(u) 的数学表述为:SG(u)=mex{SG(v)∣v∈Γ(u)}(其中 Γ(u) 表示 u 的后继状态集合)。
例如,已知状态 u 的后继状态为 v1,v2,v3,…,vn,则 SG(u) = mex {SG(v1), SG(v2), SG(v3), …, SG(vn)}。易知,mex{0,1,3}=2。

● SG 定理(Sprague-Grundy Theorem)
任何无偏博弈都可以等价转换为一个 Nim 堆,其大小等于该博弈状态的 SG 值。

● 针对普通的 Nim 博弈而言,每个堆的 SG 值就是 ai 本身。若 SG(a1)⊕SG(a2)⊕⋯⊕SG(an)≠0,先手必胜。若 SG(a1)⊕SG(a2)⊕⋯⊕SG(an)=0,先手必败。其中,a1, a2, …, an,分别为 n 堆石子的数量。

● 树上阶梯 Nim = 把奇数深度的结点当成普通 Nim 堆。每次最多移 L 个 → SG = x mod (L+1)。总 SG = 所有有效节点 SG 值异或。异或非零必胜,为零必败​​​​​​​。

【算法代码】

#include<bits/stdc++.h>
using namespace std;

const int N=1e4+5;
int cnt[N]; //number of stones at each node
int dep[N];
vector<int> G[N];
int n,T,L;
int mod;

int sg(int x) {
    return x % mod;
}

void dfs(int u) {
    for(int v:G[u]) {
        dep[v]=dep[u]+1;
        dfs(v);
    }
}

int main() {
    cin>>n>>T>>L;
    mod=L+1;
    for(int i=2; i<=n; i++) {
        int p;
        cin>>p>>cnt[i];
        G[p].push_back(i);
    }

    dep[1]=0; //Root depth is 0
    dfs(1);

    int xor_sum=0;
    for(int i=2; i<=n; i++) {
        if(dep[i]%2==1) {
            xor_sum^=sg(cnt[i]);
        }
    }

    while(T--) {
        int x,y;
        cin>>x>>y;
        if(dep[x]%2==1) { //Delete old contributions
            xor_sum^=sg(cnt[x]);
        }
        cnt[x]=y; //Update the number of stones
        if(dep[x]%2==1) { //Add new contribution
            xor_sum^=sg(cnt[x]);
        }

        cout<<(xor_sum!=0 ? "Yes":"No")<<endl;
    }

    return 0;
}

/*
in:
3 2 10
1 5
1 3
2 3
3 1

out:
No
Yes
*/

 

 

【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/158767991
https://blog.csdn.net/tenkuo/article/details/151392853
https://www.acwing.com/solution/content/25996/
https://www.acwing.com/file_system/file/content/whole/index/content/12084580/
https://www.acwing.com/solution/content/13191/
https://www.acwing.com/problem/content/893/

posted @ 2026-03-13 10:35  Triwa  阅读(0)  评论(0)    收藏  举报