BZOJ1228: [SDOI2009]E&D

题目描述

小E 与小W 进行一项名为“E&D”游戏。游戏的规则如下:桌子上有2n 堆石子,编号为1..2n。其中,为了方便起见,我们将第2k-1 堆与第2k 堆(1 ≤ k ≤ n)视为同一组。第i堆的石子个数用一个正整数Si表示。一次分割操作指的是,从桌子上任取一堆石子,将其移走。然后分割它同一组的另一堆石子,从中取出若干个石子放在被移走的位置,组成新的一堆。操作完成后,所有堆的石子数必须保证大于0。显然,被分割的一堆的石子数至少要为2。两个人轮流进行分割操作。如果轮到某人进行操作时,所有堆的石子数均为1,则此时没有石子可以操作,判此人输掉比赛。小E 进行第一次分割。他想知道,是否存在某种策略使得他一定能战胜小W。因此,他求助于小F,也就是你,请你告诉他是否存在必胜策略。例如,假设初始时桌子上有4 堆石子,数量分别为1,2,3,1。小E可以选择移走第1堆,然后将第2堆分割(只能分出1 个石子)。接下来,小W 只能选择移走第4 堆,然后将第3 堆分割为1 和2。最后轮到小E,他只能移走后两堆中数量为1 的一堆,将另一堆分割为1 和1。这样,轮到小W 时,所有堆的数量均为1,则他输掉了比赛。故小E 存在必胜策略。


输入

的第一行是一个正整数T(T ≤ 20),表示测试数据数量。接下来有T组数据。对于每组数据,第一行是一个正整数N,表示桌子上共有N堆石子。其中,输入数据保证N是偶数。第二行有N个正整数S1..SN,分别表示每一堆的石子数。


输出

包含T 行。对于每组数据,如果小E 必胜,则输出一行”YES”,否则输出”NO”。


Solution

其实就是SG函数的模板题了
一般SG函数有两种求法:
1.暴力求mex()
2.打表找规律
首先发现暴力是 O(n3) O ( n 3 )
把表打出来

printf("   ");
    for(int i=1;i<=20;++i) printf("%3d",i);
    puts("");
    for(int i=1;i<=20;++i){
        for(int j=1;j<=i;++j){
            memset(vis,0,sizeof(vis));
            for(int k=1;k<=i>>1;++k){
                vis[sg[i-k][k]]=1;
            }
            for(int k=1;k<=j>>1;++k){
                vis[sg[j-k][k]]=1;
            }
            sg[i][j]=sg[j][i]=mex();
        }
    }
    for(int i=1;i<=20;++i){
        printf("%2d:",i);
        for(int j=1;j<=20;++j){
            printf("%3d",sg[i][j]);
        }
        puts("");
    }

BZOJ1228: [SDOI2009]E&D_第1张图片
首先发现如果i&1&&j&1那么sg[i][j]=0
然后发现每个数字都是以倒三角分布的
BZOJ1228: [SDOI2009]E&D_第2张图片
对于每一个倒三角,我们将它补成正方形
BZOJ1228: [SDOI2009]E&D_第3张图片
BZOJ1228: [SDOI2009]E&D_第4张图片
BZOJ1228: [SDOI2009]E&D_第5张图片
我们发现整个表被平均分成了四部分的网状
每个数字都出现在第二象限
把所有数字的分布放在一起
BZOJ1228: [SDOI2009]E&D_第6张图片
我们基本就可以发现规律了
对于表上的点 (i,j) ( i , j ) ,我们要找到最小能够覆盖到这个点的正方形
正方形最多只有 logn l o g n
所以 O(logn) O ( l o g n ) 求出SG值就好了

#include
#include
#include
using namespace std;
inline int read(){
    int ret=0,ff=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*ff;
}
long long p2[35];
int calsg(int a,int b){
    if(a&1&&b&1) return 0;
    for(int i=1;;++i){
        int c=a&p2[i+1],d=b&p2[i+1];//用取模也可以,卡一下常
        if(c==0||d==0) continue;//取模范围是[0,p2[i+1]),而我们表的范围是[1,p2[i+1]]
        //当c==0的时候,实际上c应该等于p2[i+1],但是p2[i+1]一定不满足下面的条件,就可以直接continue;
        if(c<=p2[i]+1&&d<=p2[i]+1)return i;
    }
}
long long tmp=1;
void init(){
    p2[0]=1;
    for(int i=1;i<=32;++i) p2[i]=(tmp<<=1)-1;
}
int main(){
    //freopen("bzoj1228.in","r",stdin);
    int T=read(),ans,n;
    init();
    while(T--){
        n=read()>>1,ans=0;
        for(int i=1;i<=n;++i){
            ans^=calsg(read(),read());
        }
        ans?puts("YES"):puts("NO");
    }
    return 0;
}

你可能感兴趣的:(数学,博弈)