题解 【SNOI2020】取石子游戏

· · 题解

为什么全是打表找规律啊,这不是经典模型吗/yiw

给一个更自然的数位 DP 做法,同时附上 Fibonacci Nim 的性质证明。

先不考虑第一手不能取超过 k 个的限制,日后再说。

然后通过 n:=n-1 去掉该死的 Anti 游戏。

于是问题变成了经典 Fibonacci Nim,存在结论: Fibonacci Nim 先手必败当且仅当石子个数是 Fibonacci 数

以下我们给出证明。(认为 fib_1 = 1,fib_2 = 2)。

引理一:fib_{i+1} < 2fib_i < fib_{i+2}

引理二:\frac{4}{3}fib_{i} < fib_{i+1}

引理三:(齐肯多夫定理)任意正整数可以被拆分为若干个不连续的 Fibonacci 数之和

定理一:当石子个数是 Fibonacci 数时先手必败。

定理二:当石子个数不是 Fibonacci 数的时候先手必胜。

现在预备知识应当是足够了。我们先加上第一次取 \le k 的个数限制:

问题现在被转化成了这个样子:设 \text{lowbit(n)}n 的齐肯多夫表示法下最小的 Fibonacci 项,则:

\text{ans} = \sum_{i=1}^n[k\ge\text{lowbit(i)}]

考虑 数位 DP

然后发现这个玩意好做的不得了啊,直接记搜数位 DP 写就完了,一点难度都没有。

复杂度 O(T\log n)

Code:

#include <cstdio>
#include <cstring>
typedef long long ll;
int T;bool v[105];
ll n,k,f[105],dp[105];
ll DP(int p,bool bound,bool lst){
    if(p < k)return 1;
    if(!lst&&!bound&&~dp[p])return dp[p];
    ll r = 0;
    r += DP(p-1,bound&(v[p]==0),0);
    if(!lst && (!bound || v[p] == 1))
        r += DP(p-1,bound,1);
    if(!lst&&!bound)dp[p] = r;
    return r;
}
int main(){
    scanf("%d",&T);
    f[1] = 1,f[2] = 2;
    for(int i=3;i<=90;++i)f[i] = f[i-1] + f[i-2];
    while(T--){
        scanf("%lld %lld",&k,&n),--n;
        ll tn = n;memset(v,0,sizeof(v));
        ll tk = k;for(int i=90;i;--i)if(f[i]>tk)k=i;
        for(int i=90;i;--i)if(tn>=f[i])tn-=f[i],v[i]=1;
        memset(dp,-1,sizeof(dp)),printf("%lld\n",n-DP(90,1,0)+1);
        //为什么 +1:因为上面的 dp 不可避免的会将 0 计入答案,需要去掉。
    }
    return 0;
}