BZOJ 1188: [HNOI2007]分裂遊戲(multi-nim)

自為風月馬前卒發表於2018-02-25
Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 1386  Solved: 840
[Submit][Status][Discuss]

Description

聰聰和睿睿最近迷上了一款叫做分裂的遊戲。該遊戲的規則試:共有n個瓶子,標號為0,1,2.....n-1,第i個瓶子中
裝有p[i]顆巧克力豆,兩個人輪流取豆子,每一輪每人選擇3個瓶子。標號為i,j,k,並要保證i<j,j<=k且第i個瓶子
中至少要有1顆巧克力豆,隨後這個人從第i個瓶子中拿走一顆豆子並在j,k中各放入一粒豆子(j可能等於k)。如
果輪到某人而他無法按規則取豆子,那麼他將輸掉比賽。勝利者可以拿走所有的巧克力豆!兩人最後決定由聰聰先
取豆子,為了能夠得到最終的巧克力豆,聰聰自然希望贏得比賽。他思考了一下,發現在有的情況下,先拿的人一
定有辦法取勝,但是他不知道對於其他情況是否有必勝策略,更不知道第一步該如何取。他決定偷偷請教聰明的你
,希望你能告訴他,在給定每個瓶子中的最初豆子數後是否能讓自己得到所有巧克力豆,他還希望你告訴他第一步
該如何取,並且為了必勝,第一步有多少種取法?
假定 1 < n < = 21,p[i] < = 10000

Input

輸入檔案第一行是一個整數t表示測試資料的組數,
接下來為t組測試資料(t<=10)。
每組測試資料的第一行是瓶子的個數n,
接下來的一行有n個由空格隔開的非負整數,表示每個瓶子中的豆子數。

Output

對於每組測試資料,輸出包括兩行,
第一行為用一個空格兩兩隔開的三個整數,表示要想贏得遊戲,
第一步應該選取的3個瓶子的編號i,j,k,
如果有多組符合要求的解,那麼輸出字典序最小的一組。
如果無論如何都無法贏得遊戲,那麼輸出用一個空格兩兩隔開的三個-1。
第二行表示要想確保贏得比賽,第一步有多少種不同的取法。

Sample Input

2
4
1 0 1 5000
3
0 0 1

Sample Output

0 2 3
1
-1 -1 -1
0

HINT

 

Source

 

又一道神題,一開始一直在分析最後一堆和倒數第二堆,分析出了一坨沒鳥用的性質

首先,我們按照套路,觀察有沒有模仿棋性質的操作,發現當豆子個數為偶數的時候後手可以把先手抵消掉

這樣的話豆子數實際就變成了一串01序列

我們此時回過頭來考慮拿豆子的操作,實際上就是一個multi-nim的模型,然後這題就可做了

因為處理的時候需要用到後面的SG函式,所以用記憶化搜尋

輸出方案的話。

暴力列舉第一個的位置,然後用異或的性質判斷一下

 

 

#include<cstdio>
#include<cstring>
const int MAXN=1001;
inline char nc()
{
    static char buf[MAXN*100],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN*100,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
    char c=nc();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
    return x*f;
}
int N,S[MAXN],SG[MAXN];//遊戲可以看做是每個位置獨立進行的
int a[MAXN];
int dfs(int now)
{
    if(SG[now]!=-1) return SG[now];
    memset(S,0,sizeof(S));
    for(int i=now+1;i<=N;i++)
        for(int j=i;j<=N;j++)
            S[ (dfs(i)^dfs(j)) ] = 1;
    for(int i=0;;i++) if(!S[i]) {SG[now]=i;break;}
    return SG[now];
} 
int main()
{
    #ifdef WIN32
    freopen("a.in","r",stdin);
    #else
    #endif
    int QwQ=read();
    while(QwQ--)
    {
        memset(SG,-1,sizeof(SG));
        N=read();
        for(int i=1;i<=N;i++) a[i]=read();
        for(int i=1;i<=N;i++) 
                if(a[i]&1) dfs(i);    
        int ans=0,tot=0;
        for(int i=1;i<=N;i++) if(a[i]&1)ans=(ans^dfs(i));
        for(int i=1;i<=N;i++)
            for(int j=i+1;j<=N;j++)
                for(int k=j;k<=N;k++)
                {
                    if( (ans^dfs(i)^dfs(j)^dfs(k) )!=0) continue;
                    tot++;
                    if(tot==1) printf("%d %d %d\n",i-1,j-1,k-1);
                }
        if(tot==0) printf("-1 -1 -1\n");
        printf("%d\n",tot);
    }

    return 0;
}

 

相關文章