首先把一个数n表示成若干个菲波拉契数的和。我们这样寻找这些菲波拉契数
while(n!=0)
{
找到不大于n的最大的菲波拉契数K
n=n-K
}
对于第i菲波拉契数,如果在我们找到的数种,就把第i位置1,否则置0,那么我们就可以用一个二进制数表示一个数了(这种方法在《具体数学》中菲波拉契数列那一章中有所提及)
比如
1=1
2=10
3=100
4=101 (3+1)
比如我们有如下数: 100011,由菲波拉契数的性质我们知道,可以把一个的i位的1去掉,把i-1和i-2位都加1,那么就有:
100011=011011
同理也可以变回去
100011=100100
因为我们找这些数的时候总是找最大的那个菲波拉契数,所以可以不用考虑第二种情况。(我们最后的二进制表示中不会出现连续的1)
现在只考虑把1个1变成2个1的情况。如果一个1后面有连续i个0,那么就有i/2下整中变法,比如
100000=011000=01011
而且变过之后,原本是1的位置成为了0
其实我们构造的二进制数中有n个1,且第i个1后面紧跟连续a[i]个0,i从右往左数
比如数为:1001000110
a数组为:1,0,3,2
用dp[i][0]表示考虑前i个,把第i个1变为0有多少种变法,dp[i][1]表述不把第i个1变0有多少种变法
dp[i][1]=dp[i-1][0]+dp[i-1][1]
dp[i][0]=dp[i-1][0]*((a[i]+1)/2)+dp[i-1][1]*(a[i]/2) 第一项中因为i-1项变了0,所以i项后面连续的0多了一个。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <set>
#include <cmath>
#include <iostream>
#define maxn 509
#define INF 1e6
using namespace std;
__int64 fib[maxn],n;
__int64 dp[maxn][2];
bool vis[100];int a[maxn];
int bin(__int64 x)
{
int L=1,R=90;
while(L<R)
{
int M=(L+R+1)>>1;
if(fib[M]<=x)
L=M;
else
R=M-1;
}
return L;
}
int main()
{
fib[1]=1;fib[2]=2;
for(int i=3;i<=90;i++)
{
fib[i]=fib[i-1]+fib[i-2];
}
int tt;
scanf("%d",&tt);
while(tt--)
{
scanf("%I64d",&n);
memset(vis,0,sizeof(vis));
int mx=0,tot=0;
while(n)
{
int pos=bin(n);
mx=max(mx,pos);
n-=fib[pos];
vis[pos]=1;
}
int cnt=0;
for(int i=1;i<=mx;i++)
{
if(!vis[i])
cnt++;
else
{
a[tot++]=cnt;
cnt=0;
}
}
dp[0][0]=a[0]/2;dp[0][1]=1;
for(int i=1;i<tot;i++)
{
dp[i][0]=dp[i-1][1]*(a[i]/2)+dp[i-1][0]*((a[i]+1)/2);
dp[i][1]=dp[i-1][1]+dp[i-1][0];
}
printf("%I64d\n",dp[tot-1][0]+dp[tot-1][1]);
}
//system("pause");
return 0;
}