题目链接:B-Byfibonacci_GDCPC广东省大学生程序设计竞赛(正式赛) (nowcoder.com)
题意:
给一个数x,问有多少个 Fibonacci 子集其和为x
思路:
如果 n 的范围较小,那就是01背包,但 n<=1e7 就不能以值域作为状态,但我们可以发现看题解 :设 w , ww 为小于 x 的 Fibonacci 最大值和次大值,那么符合条件的子集有且仅有他们中的一个。
这个题有一个点,就是每个数字,不可能不由小于本身的最大两个fib组成。
然后我们每次就枚举整个序列最大值是小于本身的第一个fib,还是第二个fib即可。
但是我们要注意,次大的fib会计算重复。但是又最多用两次,所以可以容斥减掉。
找到这个规律后就可以DP了,我们令
dp[0][i] 表示 i 选取最大的那个斐波那契数的乘积和
dp[1][i] 表示 i 选取次大的那个斐波那契数的乘积和
所以转移方程则为
dp[0][i]=(dp[0][i-w]+dp[1][i-w])*w
dp[1][i]=(dp[0][i-ww]+dp[1][i-ww])*ww; i<ww*2
dp[1][i]=(dp[1][i-ww])*ww; i>=ww*2
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int N=100+10;
int a[N];
int f[3][10000008];
signed main(){
a[1]=1;
a[2]=1;
for(int i=3;i<=40;i++){
a[i]=a[i-1]+a[i-2];
}
// cout<<a[30]<<"\n";
f[0][0]=1;
f[0][1]=1;
f[1][1]=1;
int j=0;
for(int i=2;i<=1e7;i++){
if(i>=a[j]){
while(i>=a[j]){
j++;
}
j--;
}
int w=a[j];
int ww=a[j-1];
f[0][i]=(f[0][i-w]+f[1][i-w])*w;
f[0][i]%=mod;
if(i-ww<ww){
f[1][i]=(f[0][i-ww]+f[1][i-ww])*ww;
}
else {
f[1][i]=(f[1][i-ww])*ww;
}
f[1][i]%=mod;
}
int t;
cin>>t;
while(t--){
int n;
cin>>n;
cout<<(f[0][n]+f[1][n])%mod<<"\n";
}
}