题意
有一个容量为 (n) 的背包 (( n le 10^5)).
有 (n) 种物品, 第 (i) 种物品有 (i) 个, 体积为 (i).
求将背包装满的方案数.
思路
直接多重背包肯定不行, 加前缀和优化也有 (n^2).
考虑到, 体积大于 (sqrt{n}) 的物品一定不会用完, 所以相当于一个无限背包,
对体积小于 (sqrt{n}) 的物品, 做一个前缀和优化的多重背包就行了, 复杂度为 (O(n)).
对于体积大于等于 (sqrt{n}) 的物品, 直接做无限背包肯定也不行,
考虑到这些物品有一个性质 : 它们的体积是递增的, 那我们可以把无限背包的过程转化为以下两个操作,
- 往背包里加入一个体积为 (sqrt{n}) 的物品.
- 把背包里所有物品的体积都 +1.
无限背包的过程其实就相当于维护了一个递增序列, 而上述的两个操作也可以构造出所有递增序列, 所以它们是等价的.
所以, 我们设 (f[i][j]) 为往背包中放了 (i) 个物品, 总体积为 (j) 的方案数,
- (f[i][j]+=f[i-1][j-sqrt{n}])
- (f[i][j]+=f[i][j-1])
分别代表 往背包内加一个体积为 (sqrt{n}) 的物品 和 把背包内所有物品体积 +1.
由于这些物品的体积都大于 (sqrt{n}), 所以最多只能放 (sqrt{n}) 个物品, 因此复杂度为 (O(nsqrt{n})).
代码
#include<bits/stdc++.h>
using namespace std;
const int _=320+7;
const int __=1e5+7;
const int mod=23333333;
int n,t,f[__],sum[__],g[_][__],ans;
int main(){
//freopen("x.in","r",stdin);
cin>>n; t=ceil(sqrt(n));
f[0]=1; sum[0]=1;
for(int i=1;i<t;i++){
for(int j=i;j<=n;j++) f[j]=(f[j]+f[j-i])%mod;
for(int j=n;j>=(i+1)*i;j--) f[j]=((f[j]-f[j-(i+1)*i])%mod+mod)%mod;
}
g[0][0]=1;
for(int i=1;i<=t;i++)
for(int j=0;j<=n;j++){
if(j>=i) g[i][j]=(g[i][j]+g[i][j-i])%mod;
if(j>=t) g[i][j]=(g[i][j]+g[i-1][j-t])%mod;
}
for(int i=1;i<=t;i++)
for(int j=0;j<=n;j++)
g[0][j]=(g[0][j]+g[i][j])%mod;
for(int j=0;j<=n;j++)
ans=(ans+(long long)f[j]*g[0][n-j]%mod)%mod;
printf("%d
",ans);
return 0;
}