POJ 3710:Christmas Game

Description

在树上挂几个环,每一个环只与树有一个公共点,环与环之间无公共边,每次删掉一条边,把不与根节点相连的部分删除,不能操作者输,问是否先手必胜。
题面

Solution

由于环是在叶子上的,所以这个环可以单独考虑
假设这个环是奇环,那么砍掉任意一条边之后,就会变成从叶子节点延伸出来的两条链,且这两条链奇偶性相同,所以后继状态的 \(SG\) 异或起来不可能为奇数,并且可以为 \(0\),所以 \(SG=mex(SG[u])=1\)
假设这个环是偶环,那么这两条链的奇偶性不同,所以异或起来的所有情况中一定没有 \(0\),所以 \(SG=mex=0\)
那么我们就可以把环缩成一个点了
假设是偶环,实际上就没有贡献了,直接把它去掉就可以了
假设是奇环,那么会有 \(1\) 的贡献,我们直接在这个叶子节点后面接上一个点就可以达到这样的效果了

那么现在问题就转化为了树上删边游戏,利用结论:节点 \(x\)\(SG\) 等于所有儿子 \(u\)\((SG[u]+1)\) 的异或和
这样就推出了根节点的 \(SG\)
因为有很多棵树,再把所有根节点的 \(SG\) 异或起来就行了

还有就是这个题仿佛是可以有点双的?

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
using namespace std;
const int N=2010;
int n,m,T,sz[N],head[N],to[N*2],nxt[N*2],num=1;bool vis[N];
int dfn[N],low[N],DFN=0,st[N],top=0,sum,q[N],r=0;
inline void Clear(){
	num=1;DFN=sum=0;
	for(register int i=0;i<N;i++)sz[i]=head[i]=dfn[i]=low[i]=vis[i]=0;
}
inline void link(int x,int y){nxt[++num]=head[x];to[num]=y;head[x]=num;}
inline void tarjan(int x,int last){
	dfn[x]=low[x]=++DFN;st[++top]=x;
	for(int i=head[x];i;i=nxt[i]){
		if(i==last || to[i]>n)continue;
		int u=to[i];
		if(!dfn[u]){
			tarjan(u,i^1),low[x]=min(low[x],low[u]);		
			if(low[u]>=dfn[x]){
				int sz=2;r=0;
				while(top && st[top]!=u)q[++r]=st[top--],sz++;
				if(top)q[++r]=st[top--];
				if(sz>2)while(r)vis[q[r--]]=1;
				if(sz>2 && (sz&1))link(x,++sum);
			}
		}
		else low[x]=min(low[x],dfn[u]);
	}
}
inline int dfs(int x,int last){
	int ret=0;
	for(int i=head[x];i;i=nxt[i]){
		int u=to[i];
		if(u==last || vis[u])continue;
		ret^=(dfs(u,x)+1);
	}
	return ret;
}
inline int work(){
	Clear();
	int x,y;
	scanf("%d%d",&n,&m);sum=n;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		link(x,y);link(y,x);
	}
	tarjan(1,0);
	return dfs(1,1);
}
int main(){
	freopen("pp.in","r",stdin);
	freopen("pp.out","w",stdout);
	while(~scanf("%d",&T)){
		int ans=0;
		while(T--)ans^=work();
		if(ans)puts("Sally");
		else puts("Harry");
	}
	return 0;
}

posted @ 2018-07-15 09:00  PIPIBoss  阅读(237)  评论(0编辑  收藏  举报