1. 程式人生 > >hdu 5724 Chess (SG函式)

hdu 5724 Chess (SG函式)

題目連結:hdu 5724

題意:有一個n行20列的棋盤,棋盤上分佈著一些棋子,A、B兩人輪流下棋,A先手,每次操作可以將某個棋子放到自己右邊的第一個空位(也就是說右邊如果已經有子,可以跳過它,沒有就右移一步),但最多20列,絕對不能超過棋盤,無棋可走的輸。

題解:進行狀態壓縮,bit來表示在一行中一個點有沒有棋子,有棋子為1,沒有棋子為0,0到(2^20-1)就代表全了所有的可能。

我們還知道1是必敗點SG[1]=0。可以拿來驗證。

程式碼:

///SG函式模板
///類似於NIM遊戲,異或和為0為平衡狀態,異或和不為0位不平衡狀態
///先手的在不平衡狀態下能贏,

///SG值的意義。當g(x)=k時,表明對於任意一個0<=i<k,都存在x的一個後繼y滿足g(y)=i。
///也 就是說,當某枚棋子的SG值是k時,我們可以把它變成0、變成1、……、變成k-1

///對於n個棋子,設它們對應的頂點的SG值分別為(a1,a2,…,an),
///再設局面(a1,a2,…,an)時的Nim遊戲的一種必勝策略是把ai 變成k,
///那麼原遊戲的一種必勝策略就是把第i枚棋子移動到一個SG值為k的頂點。

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;

int SG[(1<<20)+100],book[30];

void get_SG()
{
    for(int i=0;i<(1<<20);i++)
    {
        memset(book,-1,sizeof(book));

        int last=-1;
        for(int j=0;j<20;j++)
        {
            if(!((i>>j)&1)) ///空格在最右的位置
                last=j;
            if((i>>j)&1)   ///最右棋子的位置
            {
                if(last!=-1)
                    book[SG[(i^(1<<j))^(1<<last)]]=true; ///後繼狀態標記
            ///找到最右邊的棋子以及可移動的空格,然後互換成後繼並標記,互換後一定比互換前的值小,因為我們是先找空格再找棋子的
            ///
            }
        }

        int item=0;
        while(book[item]!=-1) item++; ///找出最小的不屬於這個集合的非負整數

//        printf("item=%d\n",item);
        SG[i]=item;
    }
}
int main()
{
    memset(SG,0,sizeof(SG));
    get_SG();


    int ncase;
    scanf("%d",&ncase);

    while(ncase--)
    {
        int n,m,ans=0,item;

        scanf("%d",&n);

        for(int i=1;i<=n;i++)
        {
            scanf("%d",&m);
            item=0;

            for(int j=1;j<=m;j++){
                int x;
                scanf("%d",&x);

                item^=1<<(20-x);
            }
//            printf("%d\n",SG[item]);
            ans^=SG[item];
        }
//    printf("ans=%d\n",ans);
        if(ans) printf("YES\n");
        else printf("NO\n");
    }

    return 0;
}