https://nanti.jisuanke.com/t/16619
稍稍运用一下数学知识发现题目要求的是选出的集合每个元素+1之后的乘积等于2^P的方案数,取个log就变成了↓
在1~N选若干个数使得总和等于P,求方案数
证明:
设集合S的价值为f(S),当n=2时,设选了i和j号百合,集合S为{2^i-1,2^j-1}, f(S)=1(空集)+(2^i-1)+(2^j-1)+(2^i-1)*(2^j-1)=2^(i+j)
由递推关系
变形得
证毕。
然后用普通的背包DP可以就拿到60分了
然后我们发现,由于物品大小是1~N,所以最多选取O(sqrt(P))个物品,背包就满了
满分做法可以用状态f[i][j]表示选i个物品,占容量为j的方案数
由于每个背包是不同的,所以根据已选的最小的物品分类讨论一下:
如果最小的物品是1,相当于i-1个物品凑出了j-i的大小,然后整体+1
如果最小的物品不是1,相当于i个物品凑出了j-i的大小,然后整体+1
需要注意我们要防止出现选择了大小为N+1的物品的情况,所以需要减去
得到递推式f[i][j]=f[i-1][j-i]+f[i][j-i]-f[i-1][j-(N+1)]
这个算法的精髓是巧妙地利用了所有物品的价值取遍了{1,...,n}中的所有自然数。
时间复杂度O(Nsqrt(N))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=998244353;
int f[450][100010];
int main()
{
int n,p;
scanf("%d%d",&n,&p);
int i,j,ans=0;
f[0][0]=1;
for(i=1;(i+1)*i/2<=p;i++)
{
for(j=i;j<=p;j++)
{
f[i][j]=f[i-1][j-i]+f[i][j-i];
if(j>=(n+1)) f[i][j]-=f[i-1][j-(n+1)];
if(f[i][j]<0) f[i][j]+=mod;
if(f[i][j]>=mod) f[i][j]-=mod;
}
ans+=f[i][p];
if(ans>=mod) ans-=mod;
}
printf("%d",ans);
}