POJ 3710 Christmas Game(Tarjan+博弈SG函數)

【題目鏈接】
http://acm.hust.edu.cn/vjudge/problem/toListProblem.action#OJId=POJ&probNum=3710&title=&source=

【解題報告】
看過賈志豪的論文之後做的一道題。
有幾個關鍵點:

1.如何處理樹上的環

tarjan算法判環,然後根據觀察所得結論對環縮點。tarjan算法相關內容可以參見我的另一篇題解。


這裏寫圖片描述
這裏寫下我的理解。
奇環可以通過去掉一條邊變成兩條長度相等的鏈,這是一個必敗局面P,它的SG值爲0.所以奇環是N局面。
當任意去掉一條邊時,一定形成SG值爲2k和2m的兩條鏈,他們的異或值一定不爲1.
所以由定義知,奇環的SG值爲1.
相對的,先手在偶環裏去掉一條邊,鏈的長度一定爲一奇一偶,,即偶環的所有子局面SG值都不爲0,所以,偶環的SG值爲0.

之後變成了這樣一個模型:
這裏寫圖片描述
有定理:

葉子節點的SG值爲0(P狀態),中間節點的SG值爲它所有t個葉子節點的SG值加一的異或值:
SG(G)=(SG(G1')+1)^(SG(G2')+1)^ ... ^(SG(Gt')+1)

關於定理的證明可以參見論文。如果一遍不好理解的話可以多看幾遍。

最後這道題目就被轉化成了經典的NIM遊戲。

附上我自己的代碼,寫的特別醜。輕拍。

【參考資料】
《組合遊戲略述——淺談SG遊戲的若干拓展及變形》–賈志豪

《博弈類題目小結》——ACM-cxlove
http://blog.csdn.net/acm_cxlove/article/details/7854526

《POJ 2186 Popular Cows(強連通分量縮點,Tarjan算法)》–gungnir
http://blog.csdn.net/gungnir0711/article/details/49867467

【參考代碼】

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
#include<cmath>
using namespace std;
const int maxn=1e4+50;
const int maxm=5e4+50;

int head[maxn],LOW[maxn],DFN[maxn],id[maxn],cnt[maxn];
int mp[110][110];
int vis_scc[maxn];
bool mark[maxn];
int  time,scc;
int n,m,k,state;
stack<int>sta;

struct edge_T{
    int to,next;
}edge[maxm];


void tarjan( int u, int pre )
{
    DFN[u]=LOW[u]=++time;
    sta.push(u);
    mark[u]=true;
    for( int k=head[u]; k!=-1; k=edge[k].next )
    {
        int v=edge[k].to;
        if(v==pre)continue;
        if(!DFN[v]  ) //v還沒有時間戳
        {
            tarjan(v,u);  //給v打上時間戳
            LOW[u]=min( LOW[u],LOW[v] );
        }
        else if(mark[v] )
        {
            LOW[u]=min( LOW[u],DFN[v] );
        }
    }
    if(LOW[u]==DFN[u])
    {
        ++scc;
        int v;
        do
        {
            cnt[scc]++;
            v=sta.top();
            sta.pop();
            id[v]=scc;
            mark[v]=false;
        }while(u!=v);
    }
}

int get_SG( int u , int pre )
{
    if(head[u]==-1)return 0;
    int ans=0;
    sta.push(u);
    for( int k=head[u]; k!=-1; k=edge[k].next )
    {
        int v=edge[k].to;
        if(mp[u][v]>1)  //偶數條重邊就直接忽略它,不再繼續搜索,因爲一定是樹葉(按照題意),並且SG值爲0
            {
                  if(mp[u][v]&1)mp[u][v]=1;
                  else continue;
            }
            if(v==pre)continue;
        if(  (id[u]==id[v])  )
        {
            if( vis_scc[id[u]] )  continue;//已經處理過這個scc
            if(cnt[id[u]]&1)ans^=1; //奇環
            else ans^=0;  //偶環
            vis_scc[id[u]]=true;
        }
        else  ans^=(get_SG(v,u)+1);//不是同一個強連通分量,繼續搜索
    }
    return ans;
}

void solve()
{
    memset(mark,0,sizeof(mark));//tarjan的初始化
    memset(DFN,0,sizeof(DFN));
    memset(vis_scc,0,sizeof(vis_scc));
    memset(cnt,0,sizeof(cnt));
    time=0; scc=0;
    while(!sta.empty())sta.pop();

    for( int i=1; i<=m; i++ ) if(!DFN[i]) tarjan(i,-1);

    memset(vis_scc,0,sizeof(vis_scc));
      int t=get_SG(1,-1); //從根節點開始DFS求出SG值
    state^=t;
     // cout<<"SG(1)= "<<t<<endl;
}

void add_edge(  int L, int R, int O )
{
      edge[O].to=R;
      edge[O].next=head[L];
      head[L]=O;
}

int main()
{
    while(~scanf("%d",&n))
    {
        state=0;
        while(n--) //n個subtree,每個求出根的SG值,然後求異或和
        {
            scanf("%d%d",&m,&k);
            memset(head,-1,sizeof head);
            memset(mp,0,sizeof(mp));
            for( int i=1; i<=k; i++ )
            {
                int a,b;
                scanf("%d%d",&a,&b);
                add_edge(  a,b,i*2-1 );
                add_edge(  b,a,i*2 );
                mp[a][b]++;
                mp[b][a]++;
            }
            solve();
        }
    //  for(  int i=1; i<=m; i++ )cout<<i<<"   "<<id[i]<<endl;
            if(state)printf("Sally\n");
        else printf("Harry\n");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章