把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6791】[SNOI2020] 取石子(斐波那契博弈+数位DP)

点此看题面

  • 有一堆石子,规定第一次只能取走不超过\(k\)个石子,随后每个人取走的石子个数不能超过上一个人的\(2\)倍。
  • 规定取走最后一个石子的人输,求\(1\sim n\)中有多少种石子个数,使得先手必胜。
  • 数据组数\(\le10^5\)\(k,n\le10^{18}\)

最后一个石子

题目中说的是取走最后一个石子的人输,这与我们传统的博弈论观念不符。实际上我一开始直接默认取走最后一个石子的人赢,然后过了样例,一交直接爆零。

但是,这道题中只有一堆石子,所以肯定没有谁会在石子个数大于\(1\)的时候故意去把石子取完。

因此,一个人输,当且仅当他取的时候只剩下一个石子。那也就是说取走倒数第二个石子的人将会获得胜利。

假设我们一开始就把这最后一个石子给拿走,那么其实又变回取走最后一个石子的人赢这个喜闻乐见的模型了。

总结一下,只要把石子个数减\(1\),取走最后一个石子的人输就被转化回取走最后一个石子的人赢。

(其实回顾一下\(Anti-Nim\)游戏,同样也要对只剩一个石子的堆特殊讨论,估计这是对于这种\(Anti-\)博弈问题的通用套路。)

斐波那契博弈

看到这种每次取的石子不能超过上次的两倍,一眼就想到斐波那契博弈

基本结论就是把石子个数进行斐波那契分解,那么第一个人必须取走其中最小的斐波那契数个石子。(证明可见:【BZOJ2275】[COCI2010] HRPA(斐波那契博弈)

这里给出了\(k\),也就是要求最小的斐波那契数小于等于\(k\),容斥一下就是用总方案数\(n\)减去最小的斐波那契数大于\(k\)的方案数。

考虑写一个类数位\(DP\),显然斐波那契分解之后也可以通过比较最高的不同位来比较两数大小,而一个\(01\)串是一个斐波那契分解的充要条件其实就是不存在两个连续的\(1\),而根据这题的条件还要让小于等于\(k\)的斐波那契数对应位全部强制填\(0\)(实际数位\(DP\)时,只要\(DP\)到这最后几位不讨论直接返回即可)。

于是这道题就做完了。

代码:\(O(Tlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define SZ 87
#define LL long long
using namespace std;
int p[SZ+5];LL k,n,Fib[SZ+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
LL f[SZ+5][SZ+5][2];I LL DP(CI x,CI lim,CI lst=0,CI fg=0)//数位DP
{
	if(x<lim) return 1;if(fg&&f[x][lim][lst]) return f[x][lim][lst];//记忆化
	LL t=DP(x-1,lim,0,fg|p[x])+((fg|p[x])&&!lst?DP(x-1,lim,1,fg):0);return fg&&(f[x][lim][lst]=t),t;//填0或填1,不能有连续两个1
}
int main()
{
	RI i,j;for(Fib[1]=1,Fib[2]=2,i=3;i<=SZ;++i) Fib[i]=Fib[i-2]+Fib[i-1];//预处理斐波那契数
	RI Tt;LL x;read(Tt);W(Tt--)
	{
		for(read(k,n),x=n-1,i=SZ;i;--i) p[i]=x>=Fib[i]?(x-=Fib[i],1):0;//将n-1斐波那契分解(给石子数减1输赢转化)
		for(i=1;Fib[i]<=k;++i);printf("%lld\n",n-DP(SZ,i));//找到大于k的最小的斐波那契数去DP,总方案数减非法方案数
	}return 0;
}
posted @ 2021-04-06 08:12  TheLostWeak  阅读(173)  评论(0编辑  收藏  举报