背包
背包
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 背包恰好相反,只需要把内层循环改为顺序遍历即可。

浙公网安备 33010602011771号