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_1\)开始:
\(INF,0,1,2,0,4,0,1,7,0,1,2,0,12,0,1,2,0,4,0,20....\)
发现其中某一些\(a_i=i-2\),之间间隔的数字是整个数列的前缀的一部分
\(a_i=i-2\)的数全部提出来。。。设第\(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);
	}
}

猜你喜欢

转载自www.cnblogs.com/Darknesses/p/13202928.html