SNOI2020 LOJ3324 取石子
题目传送门
分析:
不是很懂,一顿胡乱找规律2333
先写个暴力,设\(f[i][j]\)表示目前还有\(i\)个石子,目前这一步最多取\(j\)个,先手是否必胜
看一下转移:
\[f[i][j]=![\&_{k=1}^{j}f[i-k][2k]]
\]
不知道与和怎么写,直接写一个\(\&\)顶一下吧2333
光看这个式子就可以发现一些性质:如果\(f[i][j]\)为必败态,那么\(f[i][k](k<j)\)也一定必败
那么设\(a_i\)表示使\(f[i][a_i]\)为必败态的最大的值
打表出来找一下规律。。。从\(a_0\)开始:
\(INF,0,1,2,0,4,0,1,7,0,1,2,0,12,0,1,2,0,4,0,1,20....\)
发现其中某一些\(a_i=i-1\),之间间隔的数字是整个数列的前缀的一部分
把\(a_i=i-1\)的数全部提出来。。。设第\(i\)个数为\(g_i\),从\(g_0\)开始
\(0,1,2,4,7,12,20,33....\)
发现\(g_i=g_{i-1}+g_{i-2}+1\)
再算一下\(g_i\)之前每一个\(g_j\)的出现次数。。。(假设\(i\)为7)
\(13,8,5,3,2,1,1\)
发现是倒序的斐波那契数列
规律就找到了。。。
斐波那契数列和上面的\(g\)增长速度都是指数级别的
单次复杂度为\(O(logN)\)
总复杂度\(O(TlogN)\)
这规律真离谱
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<iostream>
#include<map>
#include<string>
#define maxn 100005
#define INF (1ll<<60)
using namespace std;
inline long long getint()
{
long long num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
long long k,N;
long long f[maxn],fib[maxn],cnt;
long long ans;
inline void solve(int x)
{
if(k>f[x])ans++;
for(int i=0;i<x;i++)if(k>f[i])ans+=fib[x-i];
}
int main()
{
f[0]=0,f[1]=1,cnt=1,fib[0]=0,fib[1]=1;
while(1)
{
cnt++;
f[cnt]=f[cnt-1]+f[cnt-2]+1;
fib[cnt]=fib[cnt-1]+fib[cnt-2];
if(f[cnt]>INF)break;
}
int T=getint();
while(T--)
{
k=getint(),N=getint();ans=0;
if(N==1){printf("0\n");continue;}
N-=2;
for(int i=cnt;i>=0;i--)if(N>=f[i])solve(i),N-=f[i]+1;
printf("%lld\n",ans);
}
}