POJ 3710 Christmas Game (Tarjan求連通分量+樹形博弈刪邊遊戲)
阿新 • • 發佈:2019-01-04
題目:有多棵樹進行刪邊博弈,注意這裡的”樹“可能存在環,形狀也許是非常詭異的。
我們利用The Fusion Principle:任何環內的節點可以融合成一點而不會改變圖的sg值。(下面我們稱它為融合原則)
融合原則允許我們把任意一個根圖簡化為一個等效的可以通過冒號原則(即Colon Principle)簡化為竹竿的樹。
我們會發現,擁有奇數條邊的環可簡化為一條邊,偶數條邊的環可簡化為一個節點。
在這一步中很明顯要求我們能找到圖中的環,而且判斷環中節點的個數,進行處理。
利用Tarjan演算法找出強連通分量,畢竟不是搞圖論的,現學現用。。。理解不深
可能出現重邊,對於重邊的處理是,如果兩點間有偶數條邊,則當作偶數環處理,將其化為一個點,否則保留。
將圖轉化成我們所要的樹之後,便是經典的刪邊遊戲了。
葉子節點的SG值為0;中間節點的SG值為它的所有子節點的SG值加1 後的異或和。
最後把多棵樹一起做 一次NIM便完成。
#include<iostream> #include<cstdio> #include<ctime> #include<cstring> #include<algorithm> #include<cstdlib> #include<vector> #define C 240 #define TIME 10 #define inf 1<<25 #define LL long long using namespace std; vector<int>edge[105]; //鄰接表 int mat[105][105]; //存放邊的數量 int low[105],dfa[105]; //Tarjan參量 int s[105],top; //堆疊 bool instack[105]; bool vis[105]; //在Tarjan找環之後,把不需要的點標記掉 void Tarjan(int u,int pre,int depth){ low[u]=dfa[u]=depth; s[top++]=u; instack[u]=true; for(int i=0;i<edge[u].size();i++){ int v=edge[u][i]; if(v==pre&&mat[u][v]>1){ //判斷重邊 if(mat[u][v]%2==0) vis[u]=true; continue; } if(!dfa[v]){ Tarjan(v,u,depth+1); low[u]=min(low[u],low[v]); } else if(v!=pre&&instack[v]) low[u]=min(low[u],dfa[v]); } if(dfa[u]==low[u]){ int cnt=1; top--; while(s[top]!=u){ vis[s[top--]]=true; cnt++; } if(cnt&&(cnt&1)) //如果節點為奇數,則保留一個點,包括u,也就是兩個點,保留一條邊 vis[s[top+1]]=false; } } int get_sg(int u,int pre){ int ret=0; for(int i=0;i<edge[u].size();i++){ int v=edge[u][i]; if(!vis[v]&&v!=pre) ret^=(1+get_sg(v,u)); } return ret; } int main(){ int k,n,m; while(scanf("%d",&k)!=EOF){ int ret=0; while(k--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) edge[i].clear(); memset(mat,0,sizeof(mat)); memset(low,0,sizeof(low)); memset(dfa,0,sizeof(dfa)); memset(instack,false,sizeof(instack)); memset(vis,false,sizeof(vis)); top=0; while(m--){ int u,v; scanf("%d%d",&u,&v); mat[u][v]++; mat[v][u]++; edge[u].push_back(v); edge[v].push_back(u); } Tarjan(1,-1,1); ret^=get_sg(1,-1); } puts(ret?"Sally":"Harry"); } return 0; }