[bzoj4035]数组游戏

题目描述

有一个长度为N的数组,甲乙两人在上面进行这样一个游戏:首先,数组上有一些格子是白的,有一些是黑的。然
后两人轮流进行操作。每次操作选择一个白色的格子,假设它的下标为x。接着,选择一个大小在1~n/x之间的整数
k,然后将下标为x、2x、…、kx的格子都进行颜色翻转。不能操作的人输。现在甲(先手)有一些询问。每次他
会给你一个数组的初始状态,你要求出对于这种初始状态他是否有必胜策略。

好题

我们直接做怎么都不好做,考虑转化问题。
把只能选白改成随便选。率先让场上没有白色的人获胜。
那么如果有人动黑,显然做一步后不会获胜(这个黑变成了白),如果该操作对这个人有利,另一个人就会做相同操作使局面恢复。
动白好像也能恢复回去?但是动白可能一步获胜,而如果不能一步获胜,对面恢复代表恢复是有利操作,那么这个人也来阻止他……
感性理解一下啦!新游戏一定不会碰黑!
那和原游戏等价。因此每个白子是独立的。
设sg(i)表示格子i有个白色子的游戏的估价函数。
根据定义,有sg(i)=mex{sg(2*i),sg(2*i)^sg(3*i)……}
我们来发现一个结论吧!
如果 ni=nj 那么sg(i)=sg(j)
归纳证明一下,i>n/2显然正确,此时sg都是1。
否则,考虑到 nij=nij
而上面是一样的,下面又一样,因此又是一样,那么sg相同。
上面的话乱写的,可以理解一下。
分块?还是好慢。
相邻块sg值相同,好像可以并成一块哦!
然后就飞起来了。
不明原因

#include<cstdio>
#include<algorithm>
#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 maxn=100000+10;
int pos[maxn],p[maxn],sg[maxn],vis[maxn];
int i,j,k,l,r,mid,t,n,m,x,y,q,tot,top,mx;
void dp(){
    vis[0]=1;
    p[0]=n+1;
    fd(i,tot,1){
        k=pos[i];
        mx=t=0;
        fd(j,top,1){
            x=p[j-1]/k;
            if (p[j-1]%k==0) x--;
            y=p[j]/k;
            if (p[j]%k==0) y--;
            if (x==y) continue;
            vis[t^sg[j]]=1;
            mx=max(mx,t^sg[j]);
            if (x%2!=y%2) t^=sg[j];
        }
        mx++;
        fo(j,0,mx)
            if (!vis[j]){
                if (sg[top]==j){
                    p[top]=k;
                }
                else{
                    p[++top]=k;
                    sg[top]=j;
                }
                break;
            }
        fo(j,1,mx) vis[j]=0;
    }
}
int main(){
    //freopen("data2.in","r",stdin);freopen("4035.out","w",stdout);
    scanf("%d%d",&n,&q);
    i=1;
    while (i<=n){
        j=n/(n/i);
        pos[++tot]=i;
        i=j+1;
    }
    dp();
    while (q--){
        t=0;
        scanf("%d",&m);
        fo(i,1,m){
            scanf("%d",&j);
            l=1;r=top;
            while (l<r){
                mid=(l+r)/2;
                if (j>=p[mid]) r=mid;else l=mid+1;
            }
            t^=sg[l];
        }
        if (t) printf("Yes\n");else printf("No\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值