正题
题目链接:http://www.51nod.com/Challenge/Problem.html#problemId=1597
题目大意
nnn种物品,第iii个大小为iii且有iii个。
求恰好填满大小为nnn的背包的方案数
解题思路
我们可以将背包分为两份,对于大小小于等于n\sqrt nn的物品,这样的物品数量不超过nnn\sqrt nnn个,所以我们可以用多重背包来做,这里要优化,我们将fi,jf_{i,j}fi,j中根据j%ij\%ij%i的不同来进行分组,这样只有组内可以相互转移且是在一个区间内转移,可以O(nn)O(n\sqrt n)O(nn)的计算出答案,然后反向枚举压缩掉iii这一维就有方程(u枚举余数
fu+p∗i=∑p−i≤k≤p−1fu+k∗if_{u+p*i}=\sum_{p-i\leq k\leq p-1}f_{u+k*i}fu+p∗i=p−i≤k≤p−1∑fu+k∗i
然后对于大于n\sqrt nn的物品,我们可以将其视为有无限个物品,所以我们可以将问题变为求将一个数字分解成若干个可以相同但是要大于n\sqrt nn的数字的和的方案数。
考虑一个集合,每次可以加入一个数字(物品)n+1\sqrt n+1n+1或者全体数字都+1+1+1(物品都变大一个)。
然后设gi,jg_{i,j}gi,j表示目前集合内数字和(选择的物品和)为iii,然后集合内数字(物品)个数为jjj,就有方程gi,j=gi−n−1,j+gi,j−1g_{i,j}=g_{i-\sqrt n-1,j}+g_{i,j-1}gi,j=gi−n−1,j+gi,j−1
然后转移即可
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+10,XJQ=23333333;
int n,T,f[N],g[2][N],s[N],ans;
int main()
{scanf("%d",&n);T=sqrt(n)+1;f[0]=1;for(int i=1;i<T;i++){for(int u=0;u<i;u++){int sum=0,t=(n-u)/i;for(int p=t;p>max(t-i,-1);p--)(sum+=f[u+i*p])%=XJQ; for(int p=t;p>=0;p--){if(p-i>=0)sum=(sum+f[u+i*(p-i)])%XJQ;sum=(sum-f[u+i*p]+XJQ)%XJQ;f[u+p*i]=(f[u+p*i]+sum)%XJQ;}}}g[0][0]=s[0]=1;for(int i=1;i<T;i++){memset(g[i&1],0,sizeof(g[i&1]));for(int j=T;j<=n;j++)g[i&1][j]=(g[~i&1][j-T]+g[i&1][j-i])%XJQ,(s[j]+=g[i&1][j])%=XJQ;}for(int i=0;i<=n;i++)(ans+=1ll*f[i]*s[n-i]%XJQ)%=XJQ;printf("%d",ans);
}