题意:
给一个数字x, x<=1e18, 求其分解成数个fib数相加的方案总数.其中fib数不能重复.
解题思路:
首先一个结论: 任意一个数,都能被分解成 数个fib数相加的形式.
另外, 在区间 [1,1e18]总共有 88个fib数.
对于一个数 x, 我们用一个长度为88的01序列来唯一表示.当前位为1则表示取,否则不取
例如 8: 00000100...0 2: 00100...0 3: 0110...0 从高位到低位.
根据 fib_i = fib_i-1 + fib_i-2 .
对于一个序列 001, 其能够被 110 替换. 我们可以总结出. 当前位为1,若其前面有两个0,则可被替换.
2个0替换一个1, 如此连续. 则若 i为1,其后面有cnt个0, 则其方案数为 cnt/2 (计算机整除,2位为1方案)
另外我们用一个数组来表示状态:
dp( i, j ) , 表示第 a[i]位为1时, j = 0,表示不取, j = 1表示取的 方案数.
j = 1 若当前位取, 则只有中方案, 则其可能是由前一位取或者不取而来.所以有:
dp( i, 1 ) = dp( i-1, 0 ) + dp( i-1, 1 )
j= 0 若当前位不取,
当其为 前一位取而来时, i前面则有 bi - bi-1 个空格, 所以方案数是 ( b_i - b_(i-1) )/2
dp( i, j ) = dp( i-1, 0 ) * ( ( b_i - b_(i-1) )/2 ) //注意整除.
当其为 前一位不取而来时,则i前面的0位增加一个,总数为 b_i - b_(i-1) + 1,所以方案数是 (b_i-b_(i-1)+1)/2
dp( i, j ) = dp( i-1, 1 ) *( (b_i - b_(i-1) + 1) /2 )
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; typedef unsigned long long LL; const LL inf = (LL)(1e18); LL f[100], dp[100][2]; int b[100], cnt; void init(){ f[0] = f[1] = 1; for(int i = 2; i <= 100; i++){ f[i] = f[i-1] + f[i-2]; if( f[i] > inf ){ cnt = i; break; } } } int main(){ init(); int T; while(scanf("%d", &T)!=EOF){ while( T-- ){ LL x; int N = 0;; scanf("%lld", &x); for(int k = cnt-1; k >= 0; k--) if( x >= f[k] ) b[N++]=k,x-=f[k]; reverse( b, b+N ); dp[0][1] = 1; dp[0][0] = (b[0]-1)/2; for(int i = 1; i < N; i++){ dp[i][1] = dp[i-1][0] + dp[i-1][1]; dp[i][0] = dp[i-1][0]*((b[i]-b[i-1])/2) + dp[i-1][1]*((b[i]-b[i-1]-1)/2); } printf("%lld\n", dp[N-1][0] + dp[N-1][1] ); } } return 0; }