背包

背包

0/1 背包

0/1 背包解决的是若干件相互独立的物品,每件放或不放的情况。

dp[0] = 1;
for (int i=1;i<=cnt;i++)
{
	for (int j=c[i];j<=s;j++)
	{
		dp[i][j] = dp[i-1][j-c[i]];
	}
}

优化掉第一维的限制,那么第二维需要倒序枚举

为什么第二维需要倒序枚举?

如果不倒序枚举,考虑在更新 \(dp[x]\) 前更新了 \(dp[y]\) 的情况。

此时 \(dp[y]\) 先被更新,\(dp[x]\) 在更新时会考虑更新后的 \(dp[y]\),会导致可能重复计算此次外层枚举的 \(i\),导致被记入多次。

因此倒序更新可以确保参与更新的每个值都是前一轮的,保证答案正确。

可行性 0/1 背包

每个状态 dp[i] 表示 能否构成总和为 \(i\)

dp[0] = 1;
for (int i=1;i<=n;i++)
{
    for (int j=k;j>=a[i];j--)
    {
        dp[j] |= dp[j-a[i]];
    }
}

bitset 优化

\(dp\) 的值只有 \(0\)\(1\),考虑 bitset 优化

bitset<200005> dp;
for (int i=1;i<=n;i++) dp[i] = 0;
dp[0] = 1;
for (int i=1;i<=ans;i++) dp = (dp << a[i]) | dp;

回退背包

例题:P4141.消失之物

普通背包转移(求方案数)。

for (int i=1;i<=n;i++)
{
    for (int j=m;j>=w[i];j--)
    {
        dp[j] += dp[j-w[i]];
    }
}

如果不取第 \(i\) 件物品:

for (int j=0;j<=m;j++)
{
    if (j < w[i]) ans[j] = dp[j];
    else ans[j] = dp[j] - ans[j-w[i]];
}

回退背包的正确性是因为,放入背包的物品是无序的。

因此我们可以把回退的物品(也就是删除的物品)“看作” 就是在最后一个。

那么对于 \(j < w[i]\),方案数不变,\(ans[j] = dp[j]\)

而对于 \(j \ge w[i]\),没有了 \(dp[j] = dp[j] + dp[j-w[i]]\) 的转移,有 \(ans[j] = dp[j] - ans[j-w[i]]\)

回退背包在 提高组模拟赛 32 中 T3 和 T4 均有应用。

0/1 背包前 k 优解

例题:P1858 多人背包

考虑设 \(f[i][j][k]\) 表示前 \(i\) 种物品,容量为 \(j\) 下的第 \(k\) 优解。

那么转移有两种,分别是和 0/1 背包相同的 \(f[i-1][j-w][*]+v\)\(f[i-1][j][*]\)

考虑这两种转移中 \(*\) 的增大,\(f\) 的值是会减小的。所以说,我们只需要完成两个有序数组的合并,维护两个指针 \(c_1,c_2\) 即可。

实际第一维可以滚动掉的。

memset(g,-0x3f,sizeof g);
memset(f,-0x3f,sizeof f);
f[0][1] = g[0][1] = 0;
for (int i=1;i<=n;i++)
{
    cin >> w >> v;
    for (int j=m;j>=w;j--)
    {
        int c1 = 1, c2 = 1;
        for (int p=1;p<=k;p++)
        {
            if (g[j-w][c1]+v > g[j][c2])
                f[j][p] = g[j-w][c1++]+v;
            else f[j][p] = g[j][c2++];
        }
    }
    memcpy(g,f,sizeof f);
}

可能需要区分没有方案(不存在第 k 优解)和解为 \(0\).

分组背包

dp[0] = 1;
for (int i=1;i<=4;i++)
{
    for (int j=s;j>=c[i];j--)
    {
        for (int k=1;k<=d[i];k++)
        {
            if (j >= c[i]*k) dp[j] += dp[j-c[i]*k];
        }
    }
}

注意第 8 行不要掉了等号,否则 \(j = c[i]*k\) 永远不成立,就永远不会使用**唯一有一种方案的 \(dp[0]\) 来更新,也就全部都会算不出方案了。

完全背包

考虑如果没有数量限制,就能使用完全背包。

dp[0] = 1;
for (int i=1;i<=4;i++)
{
    for (int j=c[i];j<=s;j++)
    {
        dp[j] += dp[j-c[i]];
    }
}

与 0/1 背包恰好相反,只需要把内层循环改为顺序遍历即可。

posted @ 2026-07-17 09:26  xiang_you  阅读(6)  评论(0)    收藏  举报