LOJ 6089 小 Y 的背包计数问题 解题报告 (动态规划)

#6089. 小 Y 的背包计数问题

题意

有一个容量为 \(n\) 的背包 \(( n \le 10^5)\).

\(n\) 种物品, 第 \(i\) 种物品有 \(i\) 个, 体积为 \(i\).

求将背包装满的方案数.

思路

考虑到, 体积大于 \(\sqrt{n}\) 的物品一定不会用完, 所以相当于一个完全背包, 但直接做完全背包复杂度也有 \(O(n^2)\), 不可行.

考虑到这些物品有一个性质 : 它们的体积是递增的, 那我们可以把无限背包的过程转化为以下两个操作,

  1. 往背包里加入一个体积为 \(\sqrt{n}\) 的物品.
  2. 把背包里所有物品的体积都 +1.

无限背包的过程其实就相当于维护了一个递增序列, 而上述的两个操作也可以构造出所有递增序列, 所以它们是等价的.

所以, 我们设 \(g[i][j]\) 为往背包中放了 \(i\) 个物品, 总体积为 \(j\) 的方案数,

  1. \(g[i][j]+=g[i-1][j-\sqrt{n}]\)
  2. \(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;
}

posted @ 2020-01-11 21:42  BruceW  阅读(204)  评论(0编辑  收藏  举报