题意:在一个有向无环图上有n个顶点,每一个顶点都只有一个棋子,有两个人,每次根据这个图只能将任意一颗
棋子移动一步
,如果到某一步玩家不能移动时,那么这个人就输.
分析:本题是最典型的有向无环图的博弈,利用dfs把所有顶点的SG值都计算出来,然后对每个棋子的SG值进行
异或运算,如果为0就是先手必败,否则就是先手必胜.
如果某个人移动到出度为0的顶点,那么他必败,在这里首先介绍一下什么是SG函数.
Sprague-Grudy定理:
令N = {0, 1, 2, 3, ...} 为自然数的集合。Sprague-Grundy 函数给游戏中的每个状态分配了一个自然数。
结点v的Grundy值等于没有在v的后继的Grundy值中出现的最小自然数.
形式上:给定一个有限子集 S ⊂ N,令mex S(最小排斥值)为没有出现在S中的最小自然数。定义mex(minimal
excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}
=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x
的后继 }。
性质:
(1)所有的终结点所对应的顶点,其SG值为0,因为它的后继集合是空集——所有终结点是必败点(P点即先
手必败点)。
(2)对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0——无论如何操作,从必败点(P点)都只能进
入必胜点(N点)//对手走完又只能把N留给我们。
(3)对于一个g(x)!=0的顶点,必定存在一个后继点y满足g(y)=0——从任何必胜点(N点)操作,至少有一
种方法可以进入必败点(P点)//就是那种我们要走的方法。
而求整个SG函数值的过程就是一个对有向无环图进行深搜过程.
其次解释一下为什么要异或。
这需要了解下Nim博弈,事实上我们可以利用SG函数转成Nim博弈。
棋子移动一步
,如果到某一步玩家不能移动时,那么这个人就输.
分析:本题是最典型的有向无环图的博弈,利用dfs把所有顶点的SG值都计算出来,然后对每个棋子的SG值进行
异或运算,如果为0就是先手必败,否则就是先手必胜.
如果某个人移动到出度为0的顶点,那么他必败,在这里首先介绍一下什么是SG函数.
Sprague-Grudy定理:
令N = {0, 1, 2, 3, ...} 为自然数的集合。Sprague-Grundy 函数给游戏中的每个状态分配了一个自然数。
结点v的Grundy值等于没有在v的后继的Grundy值中出现的最小自然数.
形式上:给定一个有限子集 S ⊂ N,令mex S(最小排斥值)为没有出现在S中的最小自然数。定义mex(minimal
excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}
=3、mex{2,3,5}=0、mex{}=0。
对于一个给定的有向无环图,定义关于图的每个顶点的Sprague-Garundy函数g如下:g(x)=mex{ g(y) | y是x
的后继 }。
性质:
(1)所有的终结点所对应的顶点,其SG值为0,因为它的后继集合是空集——所有终结点是必败点(P点即先
手必败点)。
(2)对于一个g(x)=0的顶点x,它的所有后继y都满足g(y)!=0——无论如何操作,从必败点(P点)都只能进
入必胜点(N点)//对手走完又只能把N留给我们。
(3)对于一个g(x)!=0的顶点,必定存在一个后继点y满足g(y)=0——从任何必胜点(N点)操作,至少有一
种方法可以进入必败点(P点)//就是那种我们要走的方法。
而求整个SG函数值的过程就是一个对有向无环图进行深搜过程.
其次解释一下为什么要异或。
这需要了解下Nim博弈,事实上我们可以利用SG函数转成Nim博弈。
Nim问题模型
然后解决它的思路是这样的
那么知道Nim博弈如何解决,我们如何将SG巧妙第转换为nim呢?
以上内容均来自《挑战程序设计竞赛》
所以,问题就解决啦,先DFS出SG函数(注意要用记忆化搜索哦,否则很容易超时),然后算出所有SG的异或值即可。
#include<iostream>
#include<vector>
#include<cstdio>
using namespace std;
const int maxn=1005;
vector<int>G[maxn];
int n,dp[maxn];
int dfs(int u)
{
if(dp[u]!=-1) return dp[u];
int vis[maxn];
fill(vis,vis+n,0);
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
dp[v]=dfs(v);
vis[dp[v]]=1;
}
for(int i=0;i<n;i++)
if(vis[i]==0) return i;
}
int main()
{
//freopen("in.txt","r",stdin);
while(cin>>n)
{
for(int i=0;i<=n;i++) G[i].clear(),dp[i]=-1;//清空邻接矩阵以及初始化记忆化数组
for(int i=0;i<n;i++)
{
int m,x;
cin>>m;
while(m--)
{
cin>>x;
G[i].push_back(x);//用邻接表存有向图
}
}
int q;
while(cin>>q,q)
{
int x,f=0;
while(q--)
{
cin>>x;
f^=dfs(x);//算出所有数异或的结果
}
if(f) cout<<"WIN"<<endl;
else cout<<"LOSE"<<endl;
}
}
return 0;
}