Loading

有限背包计数问题

题意:

有一个大小为 \(n\) 的背包,你有 \(n\) 种物品,第 \(i\) 种物品的大小为 \(i\) ,且有 \(i\) 个,求装满这个背包的方案数有多少?

两种方案不同,当且仅当存在至少一个数 \(i\) 满足第 \(i\) 种物品使用的数量不同。

Solution:

首先考虑一个朴素的背包,设 \(f[i][j]\) 为考虑前 \(i\) 种物品,大小和为 \(j\) 的方案数,可以很快写出转移方程。

\[f[i][j] = \sum f[i - 1][j - k \times i] (k \leq i, k \times i \leq j) \]

得到了一个 \(O(n ^ 2 \log n)\) 的做法,可以用前缀和做到 \(O(n ^ 2)\)

发现当物品的编号大于 \(\sqrt n\) 时,这个物品的个数其实时不收限制的,因为不可能取超过 \(\sqrt n\) 个,这启发我们根号分治。

对于编号小于等于 \(\sqrt n\) 的物品,我们采用朴素的 01 背包做法,利用前缀和优化做到 \(O(n \sqrt n)\)

对于编号大于 \(\sqrt n\) 的物品,我们对这一部分采用完全背包的做法,也可以做到 \(O(n \sqrt n)\)

回顾完全背包的做法,设 \(f[i][j]\) 为选了 \(i\) 个物品,大小和为 \(j\) 的方案数。

它的转移运用到了一种思想:一个序列 \(a\),可以通过每次增加一个 \(1\),或者全体加上 \(1\) 来得到,这里相当于序列 \(a\) 种最小的数是 \(\sqrt n + 1\),所以每次添加的数是 \(\sqrt n + 1\)

得到方程 ; \(f[i][j] = f[i][j - i] + f[i - 1][j - \sqrt n - 1]\)

期中 \(f[i][j - i]\) 的含义是全体加上 1,\(f[i - 1][j - \sqrt n - 1]\) 的含义是新增一个最小的数。

最后设 \(f[i]\) 为只用编号小于等于 \(\sqrt n\) 的方案,\(g[i]\) 表示只用编号大于 \(\sqrt n\) 的方案。

答案即为 \(\sum f[i] \times g[n - i]\)

cin >> n;
B = sqrt(n);

dp[0][0] = 1;
for(int i = 1; i <= B; i ++) {
    for(int j = 0; j < i; j ++) s[j] = 0;
    for(int j = 0; j <= n; j ++) {
    	int t = j % i;
    	s[t] = s[t] + dp[i - 1][j];
    	dp[i][j] = s[t];
    	if(j >= i * i) s[t] = s[t] - dp[i - 1][j - i * i];
    }
}
for(int i = 0; i <= n; i ++) f[i] = dp[B][i];

memset(dp, 0, sizeof dp);
dp[0][0] = 1;

for(int i = 1; i <= B; i ++) 
    for(int j = 0; j <= n; j ++) {
    	if(j >= i) dp[i][j] = dp[i][j] + dp[i][j - i];
    	if(j >= B + 1) dp[i][j] = dp[i][j] + dp[i - 1][j - B - 1];
        g[j] = g[j] + dp[i][j];
}
g[0] = 1;

Z ans = 0;
for(int i = 0; i <= n; i ++)
	ans = ans + f[i] * g[n - i];

cout << ans << '\n';
posted @ 2024-01-27 00:25  Svemit  阅读(98)  评论(0)    收藏  举报