设f(n,m) 表示剩余n 个石子且这次最多取m 时的NP 状态,显然这个函数在m 上是单调的,同时状态的转移产生一条对角线且随着n 的增大逐渐右移,故从下到上可以在O(n) 下算出某个点的状态。
然而这并没什么卵用,因为这道题的n≤10 8 。
考虑k=2 时的情况,P 点集合为斐波那契数列。观察这个规律的证明: 由Zeckendorf定理可知,任意正整数可以表示为不相邻的斐波那契数的和且这个组合对每个数唯一。设MIN(x) 表示x 用斐波那契数分解之后的最小的数,以下欲证f(n)=min{i|f(n,i)=P}=MIN(n) 。 显然f(2)=1=MIN(2) ,以下对n 进行归纳。 对于0<x<MIN(n) ,注意到MIN(n−x)=MIN(MIN(n)−x) 。 (1)因为MIN(MIN(n)−x)≤2x ,故0<x<MIN(n) 的转移一定达到N 点。 (2)因为MIN(n-MIN(n))>2MIN(n)MIN(n−MIN(n))>2MIN(n) ,故此时转移到了PP 点。 其中(1)的证明很模糊,将会为k/ge 2k≥2 的情况进行详细的证明,故略去。
从k=2k=2 的证明想到,为任意的kk 构造一个数列AA ,使其可以同样地唯一表示[1,n][1,n] 的数同时相邻数比大于k 。构造很简单: 设X i 表示A i 可以分解[1,X i ] 的数,那么A i+1 =X i +1 ,X i+1 =X i +1+X j |max{j|A i+1 A j >k} 。 X i+1 X i+1 A i+2 −1A i+2 A i =X i +X j +1=A i+1 +X j =A i+1 +A j+1 −1=A i+1 +A j+1 =A i−1 +A j |min{j|A i−1 A j ≤k} 以下证明对于任意A i =a+b ,MIN(a)b ≤k 。 设A j i 表示对于A i −1 的从小到大第i 个分解。 从b=1 开始推导,此时b 与MIN(A i −b) 都会落在[1,k] 上,故成立。 此后产生A j 1 次减1,期间显然成立。 当b=A j 1 +1 时,a 在之前的MIN 被完全消去,也就是说,此时的MIN(a) 变为A j 2 ,同时,由于j 1 =max{j|A j 2 A j >k} ,故此时kb≥A j 2 =MIN(a) 。 而之后在A j 2 被完全消去之前,都保持满足kb≥MIN(a) ,因为这期间产生的所有MIN 都小于A j 2 。 在A j 2 被完全消去时,推导同对A j 1 的推导,即当A j i 被消去时,b=A j i +1 。
故对于k>2 ,可以采用与k=2 时的证明相同的方法证明f(n)=MIN(n) 。 那么生成了A 之后就可以贪心分解n ,即可得出结果。 生成A 所需的复杂度为O(|A|) : A i A i−1 =A i−1 A i−1 +A j A i−1 =1+A j A i−1 ≥1+1k 故总的复杂度为O(log 1+1k n) 。
#include<iostream>using namespace std;typedef long long ll;int ar[2000010],n,k,ct=0;void cl(){ int i,a;for(scanf("%d %d",&n,&k),ar[1]=1,i=2,a=1;ar[i-1]+1<n;++i){ for(;(ll)ar[a]*k<ar[i-1];++a); ar[i]=ar[a]+ar[i-1]; } PRintf("Case %d: ",++ct); if(ar[i-1]==n)printf("lose/n"); else{ for(--i;;--i){ if(n>=ar[i])n-=ar[i]; if(!n){printf("%d/n",ar[i]);break;} } }};int main(){ int t;scanf("%d",&t); while(t--)cl(); return 0;};