LOJ 6089 小 Y 的背包计数问题 解题报告 (动态规划)
题意
有一个容量为 \(n\) 的背包 \(( n \le 10^5)\).
有 \(n\) 种物品, 第 \(i\) 种物品有 \(i\) 个, 体积为 \(i\).
求将背包装满的方案数.
思路
考虑到, 体积大于 \(\sqrt{n}\) 的物品一定不会用完, 所以相当于一个完全背包, 但直接做完全背包复杂度也有 \(O(n^2)\), 不可行.
考虑到这些物品有一个性质 : 它们的体积是递增的, 那我们可以把无限背包的过程转化为以下两个操作,
- 往背包里加入一个体积为 \(\sqrt{n}\) 的物品.
- 把背包里所有物品的体积都 +1.
无限背包的过程其实就相当于维护了一个递增序列, 而上述的两个操作也可以构造出所有递增序列, 所以它们是等价的.
所以, 我们设 \(g[i][j]\) 为往背包中放了 \(i\) 个物品, 总体积为 \(j\) 的方案数,
- \(g[i][j]+=g[i-1][j-\sqrt{n}]\)
- \(g[i][j]+=g[i][j-i]\)
分别代表 往背包内加一个体积为 \(\sqrt{n}\) 的物品 和 把背包内所有物品体积 +1.
由于这些物品的体积都大于 \(\sqrt{n}\), 所以最多只能放 \(\sqrt{n}\) 个物品, 因此复杂度为 \(O(n\sqrt{n})\).
对于体积小于等于 \(\sqrt{n}\) 的物品, 设 \(f[i][j]\) 为考虑到前 \(i\) 个物品, 装满体积为 \(j\) 的背包的方案数, 转移方程为.
\[f[i][j] = \sum_{k = 0}^{\min\{i, \lfloor j / i \rfloor \}} f[i - 1][j - k \cdot i]
\]
这里 \(f[i][j]\) 只会从 \(f[i - 1][j], f[i - 1][j - k], f[i - 1][j - 2k], \dots\) 中转移过来, 所以可以使用前缀和优化使得复杂度降为 \(O(n\sqrt{n})\).
最后答案即为
\[\sum_{j = 0}^n f[\sqrt{n}][j] \left( \sum_{i = 0}^{\sqrt{n}} g[i][n - j] \right)
\]
代码
#include<bits/stdc++.h>
using namespace std;
const int _=320+7;
const int __=1e5+7;
const int mod=23333333;
int n,t,f[__],g[_][__],ans;
int main(){
//freopen("x.in","r",stdin);
cin>>n; t=ceil(sqrt(n));
f[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\n",ans);
return 0;
}