[dp][uestc]L - 菲波拉契数制升级版
数据很大,以背包的思路数组开不下。
先定序地考虑一个菲波拉契数如fib(i)的表示法,假设i比较大,由菲波拉契数的定义可知道fib(i)=fib(i-1)+fib(i-2);要找到其它表示就继续拆分fib(i-1)或fib(i-2),假如拆分fib(i-1)得到fib(i)=2*fib(i-2)+fib(i-3),这不能保证保证互不相同的性质(补充,也许有人会考虑将这个式作为中间过程通过拆出矛盾项来保证唯一性,然而消除了一个矛盾必然产生新的矛盾,所以不行,具体地说将fib(i-2)=fib(i-3)+fib(i-4)消除了fib(i-2)的矛盾然而产生了fib(i-3)的矛盾,fib(i-3)又是由fib(i-1)得来,无法通过有限次拆分将矛盾消除),因此考虑拆fib(i-2),又得到一种表达方式fib(i)=fib(i-1)+fib(i-3)+fib(i-4);继续拆分,和前面类似,只能拆fib(i-4);不难发现每次只能拆最小的fib数,终点为fib(1)或fib(2);由上面过程可以得出:1或2+2*m=i,m是除了fib(i)本身以外其他的表示方法,上式可以得出m=(i-1)/2(/都表示向下取整,下同)。
ok,我们成功地解决了n为菲波拉契数的情况;下面考虑n为菲波拉契数的组合的情况;假设n=fib(i)+fib(j),i>j;我们知道fib(i)和fib(j)单独的情况,现在组合起来可以借鉴刚刚拆分的思想,不同的是现在可能fib(i)和fib(j)都可以拆分;假设拆分fib(j)(可能你要问,为什么要拆小的那个?原因是在fib(i)拆分中会有和fib(j)拆分冲突的情况)得到(j-1)/2+1组;现在考虑拆fib(i),和一开始一样的思想只是终点发生了变化,为确定终点(保证独立性),我们考察fib(j)的拆分情况,我们发现在(j-1)/2种拆分中都是以fib(j-1)开头的,因此终点为fib(j)或fib(j+1),可以得到等式2*m1+j或(j+1)=i,m1=(i-j)/2,m1是除了fib(i)本身以外其他的表示方法;以fib(j)开头,即fib(j)本身,终点为fib(j+1)或fib(j+2),m2=(i-j-1)/2,m2是除了fib(i)本身以外其他的表示方法。综上,含fib(i)的表示有(j-1)/2+1种,不含fib(i)的表示有m1*(j-1)/2+m2。现在推广到n=fib(i)+fib(j)+fib(k),i>j>k的情况fib(j)和fib(k)的组合已经知道了,而且分两组以fib(j)开头的和以fib(j-1)开头的,对fib(i)拆分和上一种办法一样,由此给出dp[i][0]的定义:以fib(i-1)开头的所有组合,dp[i][1]:以fib(i)开头的所有组合。于是有状态转移方程dp[i][1]=dp[j][0]+dp[j][1],dp[i][0]=(i-j-1)/2*dp[j][1]+(i-j)/2*dp[j][0],最终结果为dp[maxi][0]+dp[maxi][1]。
#include <cstdio> #include <vector> #include <algorithm> using namespace std; long long int dp[88][2];//dp[i][0] 表示comb[0~i]的所有组合的降序排列中以fib[i-1]开头的 [1]表示以fib[i]开头的 long long int fib[88]; void get_fibs() { fib[0]=1;fib[1]=1; for(int i=2;i<88;i++) fib[i]=fib[i-1]+fib[i-2]; } vector<int> comb;//组成n的fib的下标 long long int n; void solve() { comb.clear(); for(int i=87;i>0&&n;i--) if(n>=fib[i]) { n-=fib[i]; comb.push_back(i-1); } sort(comb.begin(),comb.end()); dp[0][1]=1; dp[0][0]=comb[0]/2; for(int i=1;i<comb.size();i++) { dp[i][1]=dp[i-1][0]+dp[i-1][1]; dp[i][0]=(comb[i]-comb[i-1]-1)/2*dp[i-1][1]+(comb[i]-comb[i-1])/2*dp[i-1][0]; } printf("%lld\n",dp[comb.size()-1][1]+dp[comb.size()-1][0]); } int main() { get_fibs(); int T; scanf("%d",&T); while(T--) { scanf("%lld",&n); solve(); } return 0; }