状态压缩与求sg函数

题目:hdu5724

题意:给定一个n*20的棋盘,棋盘上有若干棋子。如果一颗棋子右侧为空,则只可以向右移动一格,若非空,则可以移到第一个空的位置,两人轮流操作,不能操作者为输,问先者是否有必胜策略

解答:注意到棋盘只有20列,则可以用状态压缩存储当前的状态。预处理出所有状态的sg值。然后n个抑或一下就好了。

我的代码(每次递归求出当前的sg值):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int MAXN = 1 << 21;
int sg[MAXN];
int getnew(int a,int u,int v)
{
    return (a - (1 << u) + (1 << v));
}
int getsg(int a)
{
    if(sg[a] != -1)
        return sg[a];
    int vis[20];
    memset(vis,0,sizeof(vis));
    int ok = 1;
    for(int i = 0;i < 19;i++)
    {
        int temp = 1 << i;
        if(a & temp)
        {
           for(int j = i + 1;j <= 19;j++)
           {
               if(!(a & (1 << j)))
               {
                   vis[getsg(getnew(a,i,j))] = 1;
                   break;
               }
           }
        }
    }
    for(int i = 0;;i++)
    {
        if(!vis[i])
        {
            sg[a] = i;
            return i;
        }
    }
}
void init()
{
    memset(sg,-1,sizeof(sg));
    for(int i = 0;i <= ((1 << 20) - 1);i++)
    {
        sg[i] = getsg(i);
    }
}
int main()
{
    int T,t,n;
    scanf("%d",&T);
    init();
    while(T--)
    {
        int ans = 0;
        scanf("%d",&n);
        while(n--)
        {
            int k;
            int tmp = 0;
            scanf("%d",&k);
            while(k--)
            {
                scanf("%d",&t);
                tmp += 1<<(t-1);
            }
            ans ^= sg[tmp];
        }
        if(ans == 0)
            puts("NO");
        else
            puts("YES");
    }
    return 0;
}

别人的代码(从后往前求sg值。因为求状态值较小的值,它的后继状态的值肯定比当前的状态值大。所以从后往前求就可以了,不需要递归)

#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
const int MAXN = 1 << 20;
int n;
int sg[MAXN];
int vis[20];
int getsg(int x)
{
    memset(vis,0,sizeof(vis));
    for(int i = 0;i < 20;i++)
    {
        if(x&(1 << i))
        {
            for(int j = i + 1;j < 20;j++)
            {
                if(!(x&(1 << j)))
                {
                    vis[sg[x^(1 << i)^(1 << j)]] = 1;
                }
            }
        }
    }
    for(int i = 0;i < 20;i++)
        if(!vis[i])
        return i;
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int i = (1 << 20)-1;i >= 0;i--)
        sg[i] = getsg(i);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int ans = 0;
        for(int i = 0;i < n;i++)
        {
            int q;
            scanf("%d",&q);
            int m = 0;
            while(q--)
            {
                int x;
                scanf("%d",&x);
                m = m | (1 << (x-1));
            }
            ans = ans ^ sg[m];
        }
        if(ans)
            puts("YES");
        else
            puts("NO");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值