hdu3094 A tree game(博弈/SG函数/树形删边游戏)

题目

给你一棵n(n<=1e5)个节点的树

Alice和Bob玩游戏,Alice先手

 

每次轮到某玩家的时候,

①若该玩家无边可删,则该玩家输

②否则该玩家可以选择一条边,

删掉这条边,

将原树划分成两棵树,

一棵带根(root==1)的,一棵不带根的,

并把不带根的这棵树的所有边全删掉

 

给定这棵树,问谁必胜

思路来源

https://blog.csdn.net/clover_hxy/article/details/53836845?utm_source=blogxgwz3

https://blog.csdn.net/Code92007/article/details/87892307

IOI2009集训队论文 贾志豪《组合游戏略述——浅谈SG游戏的若干拓展及变形》

题解

又是一道AC五分钟,证明两小时的题目

 

树的删边定理

①叶子结点leaf的SG值sg[leaf]=0

②某棵树的根节点的SG值sg[root]=(sg[i]+1)^(sg[j]+1)^...^(sg[k]+1),其中i、j、k是其子节点,^是异或

看到定理先把题A了再说,回来再看证明

好了5min过去了题目A了,回来补证明了

这道题让我看完了贾志豪《组合游戏略述——浅谈SG游戏的若干拓展及变形》

博弈论真是深坑……

详细证明可参考上述论文……

 

口胡证明:

①一个节点无边可删,sg[root]=0;两个节点一条边,sg[root]=mex{sg[leaf]}=1=sg[leaf]+1,均成立

②设小于等于K个节点均成立,数归证(K+1)个节点也成立,

以下设根为点A,分A有一棵子树和A有多棵子树讨论

 

第一种情况:A只有一棵子树,且这棵树的根为点B,

计以A为根节点的全树为G,不包含A点和AB边的以B为根节点的树为GG

显然G的点数为(K+1),GG的点数为K

①删去边AB则必胜,故该局面存在sg=0的后继局面

②删去B这棵子树里的一条边E,E至少带走一个叶子结点,

使得G-E的点数<=K,且G-E和GG-E都具有完整树形

由归纳假设G-E的点数<=K和小于等于K个节点均成立,

设sg[树(GG-E)中的根B]=P,则sg[树(G-E)中的根A]=sg[树(GG-E)中的根B]+1=P+1,成立

③设sg[树GG中的根B]=Q,则由sg函数定义,

该树可以通过删一条边E,转移到sg值为[0,Q-1]的局面,

即sg[树(GG-E)中的根B]的值域为[0,Q-1],

由②知,sg[树(G-E)中的根A]的值域为[1,Q]

由①知,sg[树(G-E)中的根A]也可取到值0,

故sg[树(G-E)中的根A]的值域为[0,Q],

由sg函数定义,sg[树G中的根A]=Q+1=sg[树GG中的根B]+1,成立

 

第二种情况:A有若干棵子树,子树的根分别为B、C、D……

即证sg[A]=sg[B]^sg[C]^...^sg[D],

显然根节点对答案是没有影响的,那我们就把A拆点

有k棵子树就把A拆成k个点,每个点挂一棵子树

这样我们发现,在原树上断边的时候,一次最多断掉一棵A的子树,

这和在拆点之后的只挂一棵子树的A上进行操作是一样的,

于是,一棵树的操作就等价于多棵树的操作,

满足sg函数性质的游戏与nim游戏是一样的

第一种情况中已证一棵树的sg[root]=sg[子节点]+1

该情况下只需将多棵树的sg值异或起来,

即拆点之后的k个A点的sg值异或起来,等于拆点之前的A点的异或值

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map> 
using namespace std;
const int maxn=1e5+10;
int T,head[maxn],cnt;
struct edge{
	int to,nex;
}e[maxn<<1];
void init()
{
	memset(head,-1,sizeof(head));
	cnt=0;
}
void add(int u,int v)
{
	e[cnt].to=v;
	e[cnt].nex=head[u];
	head[u]=cnt++;
}
int dfs(int u,int fa)
{
	int sg=0;
	for(int i=head[u];~i;i=e[i].nex)
	{
		int v=e[i].to;
		if(v!=fa)sg^=(dfs(v,u)+1);
	}
	return sg;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		int n;
		init();
		scanf("%d",&n);
		for(int i=1;i<n;++i)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add(u,v);
			add(v,u);
		}
		int ans=0;
		ans=dfs(1,-1);//dfs(now,fa)
		if(ans)puts("Alice");
		else puts("Bob");
	} 
	return 0;
} 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值