【JZOJ4178】【NOI2015模拟YDC】游戏(阶梯nim游戏)

Problem

这里写图片描述

Input

这里写图片描述

Output

这里写图片描述

Hint

这里写图片描述

Solution

刚看到题就知道是正解想不出暴力打不出的博弈。。。
比赛时我也想了一会,但是只想到了40points。。。

40points:状压DP or 记忆化搜索

40points的m≤20,可以考虑二进制压缩状态。
对于每一位,为0表示无棋子,为1表示有棋子。
这样的话,状态数是O(2m) O ( 2 m )级别的。对于每个状态的每一位,可以处理出它会往后跳到哪一格。
设tot[i]表示状态i的必胜走法。显然,如果i的第m位为1,则肯定是必胜的。此时,i2m1 i ≥ 2 m − 1
考虑逆着DP。也就是说,对于某个必败态,将其中的某个棋子往左移动,转化为某个必胜态,使其必胜走法++。可以发现,如果我们正着跳,肯定会使状态i增大;那么我们要逆着DP,则可以逆序枚举状态i来转移。
时间复杂度:O(2mm) O ( 2 m m )

Code
#include <cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;

const int M=21,S=(1<<M)-1;
int i,j,k,m,n,s,fir,a,st;
ll tot[S];

int calc(int s)//计算状态s中有多少个棋子
{
    return s?(s&1)+calc(s>>1):0;
}
inline int pos(int x){return 1<<x-1;}//返回x的状态
inline bool in(int x){return pos(x)&i;}//判断位于x的棋子是否在状态i中
int main()
{
    scanf("%d%d",&m,&n);s=(1<<m)-1;
    fo(i,1,n)scanf("%d",&a),st+=pos(a);//st记录起始状态
    fd(i,s-1,st+1)
        if(!tot[i]&&calc(i)==n)
        {
            fir=0;
            fo(j,1,m)
            {
                if(!in(j)){fir=j;continue;}
                if((!in(m)||j==m)&&fir)
                {
                    k=i-pos(j)+pos(fir);
                    tot[k]++;
                }
            }
        }
    printf("%lld",tot[st]);
}

100points:阶梯nim

其实这题可以转化成阶梯nim。
首先,我们需要特判a[n]=m-1的情况。此时,答案为末尾连续的棋子个数。因为末尾的那堆棋子都可以一步移到m。
那么,a[n] < m-1怎么办?
譬如有如下的一块板子:
这里写图片描述
如果此时所有的棋子都聚集在m-2(所有棋子连续,最后一个棋子在m-2),那么这是一个必败态。所以可以把第m-2个位置设为第0层。
然后,我们将连续的棋子放在同一层;若碰到空格,则忽略其中一个,然后令其他的空格独踞一层。
也就是说,我们每碰到一个棋子,就加进当前层;每碰到一个空格,就令层数++。于是,两个相邻(a[i]与a[i+1])棋子的层数差即为它们间的空格数。
所以说,上图中的第0层有2个棋子(即a[5]、a[6]);第3层有3个棋子(即a[2]、a[3]、a[4]);第4层有1个棋子(即a[1])。如果我们将a[1]后移一格,它就会与a[2]、a[3]、a[4]连在一起;所以对应它往下跳一层,就可以与a[2]、a[3]、a[4]处在同一层。
于是,这就转化成了一个阶梯nim游戏。SG=奇数层棋子数的异或和。
当SG=0时必败,SG=1时必胜。但这题并不让我们判断是否必胜,而是要我们求必胜走法方案数。
我们可以分类讨论:从奇数层移到偶数层,设那个奇数层有a个棋子,此时相当于取走若干个棋子,那么只要它满足a>SG^a,我们就可以取走若干个,使它剩下SG^a个,然后SG值就会变成0,所以此时ans++;从偶数层移到奇数层,设偶数层有c个棋子,奇数层有d个棋子,只要它满足c+d≥SG^d且d < SG^d,我们就可以从c移若干个给d,把d补至SG^d个,SG值就会变成0,所以此时也让ans++。
当然,这可能有O(m) O ( m )层,我们只需记录有棋子的层的信息即可。
时间复杂度:O(n) O ( n )

Code

#include <cstdio>
#include <cctype>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;

const int N=1e6+9;
int i,j,m,n,a[N],t,b[N],c[N],d[N],SG,tot;

void read(int&x)
{
    char ch=getchar();x=0;
    for(;!isdigit(ch);ch=getchar());
    for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}
int main()
{
    read(m);read(n);
    fo(i,1,n)read(a[i]);

    if(a[n]==m-1)
    {
        fd(i,n-1,1)if(a[i]+1<a[i+1])break;
        printf("%d",n-i);
        return 0;
    }

    a[0]=-1;a[n+1]=m-1;
    for(i=n;i;i=j-1)
    {
        t+=a[i+1]-a[i]-1;
        fd(j,i,1)if(a[j-1]+1<a[j])break;
        if(t&1) b[++b[0]]=i-j+1;
        else
        if(t)
        {
            c[++c[0]]=i-j+1;
            if(a[i]+2==a[i+1])d[c[0]]=b[b[0]];
        }
    }

    fo(i,1,b[0])SG^=b[i];
    if(!SG){putchar('0');return 0;}
    fo(i,1,b[0])tot+=(b[i]>=(b[i]^SG));
    fo(i,1,c[0])tot+=(c[i]+d[i]>=(d[i]^SG)&&d[i]<(d[i]^SG));
    printf("%d",tot);
}

版权声明:本文为qq_36551189原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。