引入:树上删边博弈
例题:给出一个有 N个点的树,有一个点作为树的根节点。游戏者轮流从树中删去边,删去一条边后,不与根节点相连的部分将被移走。谁无法移动谁输。
结论:叶子节点的SG值为0;中间节点的SG值为它的所有子节点的SG值加1后的异或和。根节点SG值非0先手胜。
无向图的删边博弈
例题(poj 3710): 一个无向联通图,有一个点作为图的根,一条边最多涉及一个环,每个环只有一个结点与树相连。游戏者轮流从图中删去边,删去一条边后,不与根节点相连的部分将被移走。谁无法继续操作谁输。
输入格式:多组输入。每个测试用例的第一行是一个整数N(N<100),它表示子树的数目。第一行是节点数m(m<100)和边数k(k<500)。树的节点从1到m编号。以下每行包含2个整数a和b,表示一条边<a,b>。节点1始终是根。
输出格式:对于每组测试数据,输出胜者姓名。
结论:将图中的任意一个偶环缩成一个新点,任意一个奇环缩成一个新点加一个新边;所有连到原先环上的边全部改为与新点相连。这样的改动不会影响图的SG 值。然后就是树上删边博弈。
#include<cstdio> #include<cstring> #define min(a,b) (a<b ? a:b) int cnt,n,m,k,x,y,top,sg,tot; int head[105],dfn[105],low[105],sta[105],kk[105][105]; bool vis[105],in[105]; struct Node{ int nex,to; }e[1005]; inline void add(int a,int b){ ++cnt; e[cnt].nex = head[a]; e[cnt].to = b; head[a] = cnt; } void init(){ cnt = tot = top = 0; memset(dfn,0,sizeof(dfn)); memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); memset(kk,0,sizeof(kk)); } void tarjan(int u,int f){ sta[++top] = u, in[u] = 1; low[u] = dfn[u] = ++tot; for(int i = head[u]; i; i = e[i].nex){ int to = e[i].to; if(to == f && kk[to][u]>1){ if(kk[to][u]%2 == 0) vis[u] = 1; continue; } if(!dfn[to]){ tarjan(to,u); low[u] = min(low[u],low[to]); } else if(to != f && in[to]) low[u] = min(low[u],dfn[to]); } if(dfn[u] == low[u]){ int num = 1; while(sta[top] != u){ ++num, in[sta[top]] = 0; vis[sta[top--]] = 1; } --top, in[u] = 0; if((num&1) && num > 1) vis[sta[top+2]] = 0; } } int get_sg(int u,int f){ int sg = 0; for(int i = head[u]; i; i = e[i].nex){ int to = e[i].to; if(to != f && !vis[to]) sg ^= (get_sg(to,u)+1); } return sg; } int main(){ while(scanf("%d",&n) != EOF){ sg = 0; while(n--){ scanf("%d%d",&m,&k); init(); for(int i = 1; i <= k; ++i){ scanf("%d%d",&x,&y); add(x,y), add(y,x); ++kk[x][y], ++kk[y][x]; } tarjan(1,-1); sg ^= get_sg(1,-1); } if(sg) puts("Sally"); else puts("Harry"); } return 0; }