[bzoj4035] 数组游戏——SG函数 大佬们的博客 Some Links

题目大意:

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

思路:

发现这个题目的每个格子里面只有黑白两种状态,其实和HNOI的一道叫做分裂游戏的题目比较像,但是那个题目每个格子里面是有多个石子来取的,所以我们可以把这题的条件看成白格子的地方有一个石子,黑格子的地方无东西,我们的目的就是要把所有的石头取走。
每一个有白格子的地方单独看做是一个游戏,后继的状态即后面的若干个格子进行颜色翻转,也就是加上一个石头,SG函数转移即可。
其实也可以换一个角度去想,就是后面的格子虽然翻转了,但是我们最后要让所有的游戏求和,所以目前翻转了的不是选中的格子最后总是要翻回来的,要不然求和就和不正确,这个想法和上面的做法也是同样的。
所以可以得到转移的方程 S G [ i ] = m e x ( S G [ i 1 ] x o r S G [ i 2 ] . . . x o r . . . S G [ k ] ) k [ 2 , n i ]
但是暴力转移显然是不行的。
于是把表打出来我们发现当 n i = n j 时,即在同一个整除块内的时候SG函数的值是相等的。为什么呢?
用数学归纳法来证明: i > n 2 显然成立,这个时候都是1。
否则,因为 n i = n j ,所以有 n i k = n j k 以及 n i k = n j k ,所有它们后面的状态都是在同一个块内的,所以相等。
然后我们就可以一维用分块枚举 i ,然后在 [ 1 , n i ] 的范围内再用一次分块枚举 k 就好了。
然后到了这里我就遇到了一个问题:无法表示SG函数的状态,然后就用了一波map,果然T了。
其实发现在 i n 的时候,每一个块的端点都连续,可以用一个数组,当 i > n 时,整除出来的结果是连续的,又可以用一个数组。

/*==========================
 * Author : ylsoi
 * Problem : bzoj4035
 * Algodithm : SG
 * Time : 2018.6.3
 * ========================*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<climits>
#include<ctime>
using namespace std;
void File(){
    freopen("bzoj4035.in","r",stdin);
    freopen("bzoj4035.out","w",stdout);
}
template<typename T>bool chkmax(T &_,T __){return _<__ ? (_=__,1) : 0;}
template<typename T>bool chkmin(T &_,T __){return _>__ ? (_=__,1) : 0;}
#define REP(i,a,b) for(register int i=a;i<=b;++i)
#define DREP(i,a,b) for(register int i=a;i>=b;--i)
#define MREP(i,x) for(register int i=beg[x];i;i=E[i].last)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define inf INT_MAX
const int maxn=1e9+10;
const int maxm=100+10;
int n,q,m,a[maxm],SG[maxm*maxm],L[maxn/10000],R[maxn/10000],cnt;
int va1[maxn/10000],va2[maxn/10000],block;
bool in[maxn/10000];
int get(int x){return x<=block ? va1[x] : va2[n/x];}
void into(int x,int va){
    if(x<=block)va1[x]=va;
    else va2[n/x]=va;
}
void init(){
    for(int l=1,r;l<=n;l=r+1){
        r=n/(n/l);
        ++cnt;
        L[cnt]=l;
        R[cnt]=r;
    }
    DREP(i,cnt,1){
        mem(in);
        int b=n/L[i],sum=0;
        for(int l=1,r;l<=b;l=r+1){
            int c=b/l,tmp=get(L[i]*l);
            r=b/c;
            in[sum]=1;
            in[sum^tmp]=1;
            if((r-l+1)%2)sum^=tmp;
        }
        REP(j,0,maxn)if(!in[j]){
            into(L[i],j);
            break;
        }
    }
}
int main(){
    File();
    scanf("%d%d",&n,&q);
    block=sqrt(n);
    init();
    REP(i,1,q){
        int ans=0;
        scanf("%d",&m);
        REP(j,1,m){
            int u;
            scanf("%d",&u);
            ans^=get(u);
        }
        if(ans)puts("Yes");
        else puts("No");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ylsoi/article/details/80556083