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

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 1 2 1 3 2 3 4 3
 

Sample Output
  
  
1
 

Author
BUPT
 

Source
 

题意:

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;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值