P3235 [HNOI2014] 江南乐

蒟蒻居然不看题解写出来了黑题。

题意

首先给定一个数 $F$,有 $T$ 组数据,每组数据有 $n$ 堆石子,两个人进行博弈,每次从现有的石子堆中(包含分出来的石子堆)选择一堆石子数量不小于 $F$ 的石子堆,然后任选一个正整数 $M(M \ge 2)$,将其分为 $M$ 堆石子,并且这 $M$ 堆石子中石子数最多的一堆至多比石子数最少的一堆多 $1$(即分得尽量平均,事实上按照这样的分石子方法,选定 $M$ 和一堆石子后,它分出来的状态是固定的)。当一个玩家不能操作的时候,也就是当每一堆石子的数量都严格小于 $F$ 时,他就输掉。

所有数都是正整数,$1 \le T,N \le 100$,$1 \le F,\text{每堆石子数量} \le 10^5$。

题解

比较显然的是 $n$ 堆石子互不干扰,所以可以把每堆石子看作一个独立的游戏,最后把 $n$ 堆石子的 SG 值异或起来。

考虑求解一堆有 $i$ 个石子的 SG 值,记为 $SG_i$。显然 $\forall i<F,SG_i=0$。当 $i \ge F$ 时,朴素做法是枚举分出来的堆数 $k(2\le k \le i)$,对于每一个 $k$,令 $x \gets \lfloor \frac{i}{k} \rfloor,y \gets i \bmod k$,表示分出来的石子堆中,有 $y$ 堆包含 $x+1$ 个石子,$k-y$ 堆包含 $x$ 个石子,设这个局面的 SG 值为 $SG_
{i_k} = \underbrace{SG_{x+1} \oplus \dots \oplus SG_{x+1}}_{\text{y个}} \oplus \underbrace{SG_{x} \oplus \dots \oplus SG_{x}}_{\text{k-y个}}$,则 $SG_i = \mathop{\operatorname{mex}}\limits_{j=2}^{i} SG_{i_j}$。

这样做的复杂度太高,注意到 $\lfloor \frac{i}{k} \rfloor$ 可以用整除分块,于是 $x$ 只有 $O(\sqrt{i})$ 种取值。对于每种取值,每多分出来一堆(如果可行的话),就相当于从 $y$ 中取 $x$ 个石子单独成一堆,该局面的 SG 值为 $y-x$ 个 $SG_{x+1}$ 与 $k-y+x+1$ 个 $SG_x$ 异或,也相当于将上一个局面的 SG 值与 $x$ 个 $SG_{x+1}$ 和 $x+1$ 个 $SG_x$ 异或,注意到后面两个的异或和只与 $x$ 的奇偶有关,如果 $x$ 是奇数则异或和是 $SG_{x+1}$,否则是 $SG_x$,由于 $x$ 不变,所以每个 $x$ 至多会产生两个 SG 值。

于是可以预处理出所有 SG 值,设最大的一堆有 $v$ 个石子,则时间复杂度为 $O(v\sqrt v)$

代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e5;
int sg[N+7];
deque<int> q;
bool vis[N+7];
int main()
{
    int T,f;
    cin>>T>>f;
    for(int i=max(2,f);i<=N;++i) {  //注意这里至少要从2开始
        int l=2;
        vis[0]=1;q.push_back(0);
        while(1) {
            int x=i/l;
            if(!x)  break;
            int r=i/x,y=i-x*l;  //l是分出来的堆数,x是每堆至少多少石子,y是余下来的石子
            if(x<f-1)  break;
            int sum=0,tmp=0;
            if(y&1)  sum=sg[x+1];
            if((l-y)&1)  sum^=sg[x];
            if(!vis[sum])  vis[sum]=1,q.push_back(sum);
            if(x&1)  tmp=sg[x+1];
            else  tmp=sg[x];
            if(r-l+1>1) {
                sum^=tmp;
                if(!vis[tmp])  vis[sum]=1,q.push_back(sum);
            }  
            l=r+1;
        }
        for(int j=0;;++j)
            if(!vis[j]) {sg[i]=j;break;}
        for(int j=0;j<q.size();++j)  vis[q[j]]=0;
        q.clear();
    }
    while(T--) {
        int n;
        int ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;++i) {
            int x;
            scanf("%d",&x);
            ans^=sg[x];
        }
        ans=(ans!=0);
        printf("%d ",ans);
    }
    return 0;
}

菜就多练练