Loading

有限背包计数问题

题意:

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

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

Solution:

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

f[i][j]=f[i1][jk×i](ki,k×ij)

得到了一个 O(n2logn) 的做法,可以用前缀和做到 O(n2)

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

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

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

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

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

得到方程 ; f[i][j]=f[i][ji]+f[i1][jn1]

期中 f[i][ji] 的含义是全体加上 1,f[i1][jn1] 的含义是新增一个最小的数。

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

答案即为 f[i]×g[ni]

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 @   Svemit  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示