您的位置:首页 > 其它

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

2015-11-16 16:17 513 查看
【题目链接】

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: