sg函数+数学_________Stone game( hdu 5865 2016多校第十场 )

来源:互联网 发布:php md5解密函数 编辑:程序博客网 时间:2024/05/12 14:29

Problem Description
Birdstorm and hahaha are playing a game. Given a DAG (Directed acyclic graph) with n nodes and m edges. There are total k stones on the nodes and each node can contains more than one stone. Each turn, one can move at most two stones alone the edge, and he is lost if he can not move any one. Birdstorm move first and he wants to know how many ways to do his first operation that he can win the game.
 

Input
There are multiple test case. Each case begin with n(1<=n<=5000), m(1<=m<=100000), k(1<=k<=300). Next m lines, each lines contains two integers u, v, indicates an edge from u to v. Then a line with k numbers, indicates the position of the i-th stone.
 

Output
Each test case, print one line, the number of ways.
 

Sample Input
4 3 12 13 23 43
 

Sample Output
1
 

Author
BUPT
 

Source
2016 Multi-University Training Contest 10
 

题意:

n个点,由m个有向边连接成无环图,在图上每个节点可能有些石子,现在两个人轮流移动石子,每个人每轮最多可以选择两个棋子沿着有向边移动一步。如果轮到某人操作的时候发现已经无法操作,则输。


分析:

这是一个 NIM k 类型的游戏,在这里k为2。结论就是求出每个节点的sg值后,模三异或,其结果如果为0就是后手必赢否则先手比赢。

比赛的时候虽然没看过这个定理但是已经推出来了。但是后面计算先手第一步比赢策略种数没有算出来。

我们用pre[ i ] 表示当前异或值为i的方案数。则对于每种状态变化cnt加上其互补状态变化的方案数就是最后的答案。类似于前缀和。


代码:

#include<stdio.h>#include<string.h>#include<string>#include<iostream>#include<vector>#include<algorithm>#include<map>using namespace std;typedef long long ll;vector<int>edge[5010];int sg[5010];int n,m,k;int stone[310];int pre[60000];int td[5010];int to(int);int Xor(int,int);int getsg(int index)    //求每个点的sg值{    if(sg[index]!=-1)return sg[index];    bool vis[5010]={0};    for(int i = 0 ; i < edge[index].size() ; i ++)    {        int step = getsg(edge[index][i]);        vis[step] = 1;    }    for(int i = 0 ; i < 5010; i ++)        if(!vis[i])    {        sg[index]=i;        return i;    }}int to(int x)   //将十进制转化为二进制然后当作三进制数转化为十进制{    int num = 0;    int a[20],tot = 0;    while(x)    {        a[tot++] = x%2;        x /= 2;    }    for(int i = tot - 1 ; i >= 0 ; i --)        num = num*3 + a[i];    return num;}int Xor(int a,int b)   //模三异或{    int ans[20]={0},tot = 0;    int num = 0;    while(a>0 || b > 0)    {        ans[tot]+=a%3;        ans[tot]+=b%3;        tot ++;        a/=3;        b/=3;        ans[tot-1] %= 3;    }    for(int i = tot - 1 ; i >= 0 ; i --)        num = num*3 + ans[i];    return num;}int main(){    int u,v;    while(scanf("%d%d%d",&n,&m,&k)!=EOF)    {        for(int i = 0 ; i <= n ; i ++)        {            edge[i].clear();        }        memset(sg,-1,sizeof(sg));        for(int i = 0 ; i < m ; i ++)        {            scanf("%d%d",&u,&v);            edge[u].push_back(v);        }        for(int i = 1 ; i <= n ; i ++)        {            sg[i] = getsg(i);            td[i] = to(sg[i]);        }        int ans = 0;        for(int i = 0 ; i < k ; i ++)        {            scanf("%d",&stone[i]);            ans = Xor(ans,td[stone[i]]);        }        memset(pre,0,sizeof(pre));        if(!ans)printf("0\n");        else        {            int cnt = 0;            pre[0] = 1;  //这里令0状态种类为1,加上了先手只操作一堆的情况。            for(int i = 0 ; i < k ; i ++)            {                for(int j = 0 ; j < edge[stone[i]].size() ; j ++)                {                    int y = edge[stone[i]][j];                    int t = Xor(Xor(Xor(Xor(Xor(0,ans),ans),td[stone[i]]),td[y]),td[y]);//操作两堆要满足两堆状态变化异或ans后 == 0                     cnt += pre[t];   //找到当前状态为t的种类数                }                for(int j = 0 ; j < edge[stone[i]].size() ; j ++)//更新状态种类数                {                    int y = edge[stone[i]][j];                    pre[Xor(Xor(td[y],td[stone[i]]),td[stone[i]])] ++;                }            }            printf("%d\n",cnt);        }    }    return 0;}






0 0
原创粉丝点击