P7864-摘叶子-题解
正经教学
1.题目类型判断
有两个帅哥人,他们都需要运用最优策略以获得胜利。
每个人可以在一回合内摘叶子节点。
很明显,这是一道关于树的叶子节点的博弈论题目。
2.基本思路
结论1:若当前存在一棵树
证明:
(1) 如果开始是必胜态。因为我们是通过一个非叶子节点添加点 Extra ,可知 Extra 也是一个叶子节点,先手根据必胜态所取点加上 Extra ,留给对方的同样是必败态。
(2) 如果开始是必败态,利用非叶子节点增加一个点 Extra ,且第一次直接选择 Extra,那么必败态就送给对面了对面很喜欢。
结论2:若树 (作者觉得这个名字很美妙)) (即该叶子节点的父亲节点的子节点为叶子节点的数量
证明:
题目条件
我们把非“孤独的叶子”的一个节点的父亲的其它子节点的其中一个看作结论1证明中的 Extra ,无论去掉 Extra 后的树
根据结论1,加上了 Extra 的树 欧耶!!!
这是一个例子:
原始树是这个样子;
我们把点5看为 Extra ;
此时不管下图是否为必胜或者必败,加上 Extra 的树一定是必胜态(结论1)。
(2) 若不存在一个叶子结点父亲有多于一个儿子,考虑每个叶子(含)到深度最大的儿子个数多于1的祖先(不含)之间的点数。若所有的这些数都是偶数,那么后手必胜,因为后手可以模仿先手直到存在一个叶子结点父亲有多于一个儿子(也就是某个叶子到深度最大的儿子个数多于1的祖先只剩1的距离了),从而达到必胜态。否则先手必胜,因为先手可以一步转换为后手必胜状态。
(这位作者很懒,什么也没有画)
3.std代码
#include<bits/stdc++.h>
using namespace std;
int fa[1000005];
vector<int> leaves;
int du[1000005],Ks[1000005];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,x;
scanf("%d",&n);
leaves.clear();
for(int i=1;i<=n;i++)
{
Ks[i]=du[i]=0;
}
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
fa[i]=x;
du[x]++;
Ks[x]++;
}
for(int i=1;i<=n;i++)
if(du[i]==0) leaves.push_back(i);
bool flag=0;
for(vector<int>::iterator it=leaves.begin();it!=leaves.end();it++)
{
int v=*it;
int father=fa[v];
if(Ks[father]>=2)
{
flag=1;
break;
}
int cnt=1;
while(Ks[fa[v]]==1)
{
cnt++;
v=fa[v];
}
if(cnt&1)
{
flag=1;
break;
}
}
if(flag) printf("1\n");
else printf("0\n");
}
return 0;
}