题解:P7864 「EVOI-RD1」摘叶子

题目传送门

考虑是否有能够使胜败局面转换的性质或操作。

  1. \(G\) 中任意一个非叶子节点加上一个叶子,则新的 \(G'\) 一定是必胜态。

    • 证明:

    • 如果 \(G\) 为必胜态,则先手按照 \(G\) 的策略操作,同时把新的叶子也摘掉。则剩下的就是必败态。

    • 如果 \(G\) 为必败态,先手把新的叶子摘掉。则剩下的就是原来的必败态 \(G\)。得证。

  2. 由上面结论可知,如果一个非叶子节点有至少两个儿子,且有儿子为叶子,则可以看作 \(G\) 是由没有这个叶子的 \(G'\) 加上叶子变来的,则为必胜态。

  3. \(G\) 不满足第 2 条,则所有叶子节点的父亲都只有一个儿子。

  • 若所有的节点都只有一个儿子,也就是说树退化成了链,则容易判断。

  • 若存在有多个儿子的节点:

    • 在两个人不断摘叶子的过程中,一定会出现第 2 条的局面,这样就可以判出胜负。

    • 则我们要判断出现这种局面时,是哪一方先手。

    • 对于一个多个儿子的节点 \(u\),如果 \(u\) 子树内还有多个儿子的节点,则 \(u\) 是不用判断的(子树的的一定先达到第 2 条)。

    • 我们考察 \(u\) 儿子节点 \(v\) 到达叶子的距离(此时 \(v\) 往下都是链,所以对应叶子是唯一的),也就是 \(v\) 成为叶子的需要在这条链上操作的步数 \(cnt\)

    • 如果 \(cnt\) 为奇数,我们可以发现只要先手使 \(cnt\) 减一,后手就可以跟着把 \(cnt\) 减一。\(cnt\) 奇偶性不变。那么,最后一定是后手可以摘到 \(v\)

    • 如果 \(cnt\) 为偶数,则先手可以用一步操作把它变为奇数。这样就变成了先手跟着后手摘叶子,先手必胜。

  • 也就是说如果存在 \(cnt\) 为偶数的 \(u\),先手就把它们全都变为奇数,这样无论后手怎么操作都是必败态。相反如果全是奇数,则先手必败。

时间复杂度线性。

#include <bits/stdc++.h>
using namespace std;
#define LG(x) (31^__builtin_clz(x))
#define ll long long 
#define SIZIO (1<<22)
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,SIZIO,stdin))==rp1?EOF:*rp1++)
char buf[SIZIO+1],*rp1,*rp2;
inline int read(){
    int d=0,f=0;char ch=gc();
    while (!isdigit(ch)) f|=(ch=='-'),ch=gc();
    while (isdigit(ch)) d=(d<<1)+(d<<3)+ch-'0',ch=gc();
    return f?-d:d;
}
const int N=1000005;
int n,e[N],ne[N],h[N],idx=1,flag,hg[N],FF,st[N];
// flag 答案  FF 是否有cnt为偶数
inline void add(int a,int b) {e[idx]=b,ne[idx]=h[a],h[a]=idx++;}
void dfs(int u){
    int son=0,f=0,F=0;
    //son 儿子数 f(0/1) 是否有儿子为叶子 F(0/1) 是否有cnt为偶数
    hg[u]=st[u]=0;
    for (int i=h[u];i;i=ne[i]){
        int v=e[i];
        dfs(v),son++,st[u]|=st[v];
        hg[u]=max(hg[u],hg[v]+1);
        if (!hg[v]) f=1;  
        if (!(hg[v]&1)) F=1; 
    }
    if (st[u]) return ;
    if (son>1) {
        st[u]=1;
        if (f||F) flag=1;
    }
}
inline void solve(){
    n=read(),idx=1;flag=FF=0;
    for (int i=1;i<=n;i++) h[i]=0;
    for (int i=2;i<=n;i++) add(read(),i);
    dfs(1);
    if (hg[1]==n-1) flag|=(n&1);//链
    putchar('0'+flag);putchar('\n');
}
int main(){
    int T=read();
    while (T--) solve();
    return 0;
}

posted @ 2026-05-04 15:56  TP2010  阅读(4)  评论(0)    收藏  举报