背包 学习笔记

背包 学习笔记

甚至到退役都没有系统地学习过这个东西,唉,草台班子SDZX。

01背包

到高中毕业也只会这一种。。

不过状态转移方程还是很好写,注意如果要滚掉一维,直接倒序枚举容量即可。

例题 P1048

for(int i=1;i<=n;++i)
{
for(int w=m;w>=0;--w)
{
if(w-a[i]>=0)dp[w]=max(dp[w],dp[w-a[i]]+val[i]);
}
}

存在性背包

P1441 一点小变式,但是大概就是把01背包的转移改成了赋一个布尔值,回过头发现还没学的时候做出来一道新生赛的题竟然就是这玩意

完全背包

每个物品可以无限选,那么比较naive的想法就是再在最里面套一层枚举选取个数的循环,但奈何这种时间复杂度较高,想一想有没有更加优秀的做法。

为什么01背包不能正序枚举?因为可能会出现 m 容量时,我已经用 当前物品做了一次转移,也就是已经被用了,但是后面枚举 m+wi 这个容量的时候,可能又会从 m 这个状态 再装一个当前物品进行转移,这样从物理意义上来说,就是用了两次同一个物品,在 01背包的意义下明显是不合法的。

但是这种不合法恰恰就正好是完全背包的要求所在啊!!!所以我们正序枚举就行。

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

多重背包

不是无限个,可以选多次。

暴力地话就是完全背包的naive做法。复杂度是 O(nmk) 的。

for(int i=1;i<=n;++i)
{
for(int j=m;j>=0;--j)
{
for(int cnt=0;cnt<=num[i]&&j-cnt*w[i]>=0;++cnt)
{
dp[j]=max(dp[j],dp[j-cnt*w[i]]+val[i]*cnt);
}
}
}

二进制拆分可以减少无意义选取的次数,注意这里要求拆分的方式能够组合出所有数:复杂度 O(mki)

for(int i=1,tval,tw,tnum;i<=n;++i)
{
re(tval),re(tw),re(tnum);
for(int j=1;j<=tnum;j<<=1)
{
w[++cnt]=tw*j,val[cnt]=tval*j;
tnum-=j;
}
if(tnum)w[++cnt]=tw*tnum,val[cnt]=tval*tnum;
}
for(int i=1;i<=cnt;++i)
{
for(int j=m;j>=w[i];--j)
{
dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
}
}

还有一种比较优秀的单调队列优化,但是今晚害得做一做 cf ,挖个坑。
然而昨天晚上还是没有做 : (

//TODO

例题 P1776

分组背包

每组里面只能选取一件,直接枚举组数->枚举容量->枚举每个子物品就可以了。

for(int i=1;i<=t;++i)
{
for(int j=m;j>=0;--j)
{
for(int k=1;k<=a[i].cnt;++k)
{
if(j-a[i].w[k]>=0)dp[j]=max(dp[j],dp[j-a[i].w[k]]+a[i].val[k]);
}
}
}

费用背包

多了一维费用,实际上就是两维的背包。

for(int i=1;i<=n;++i)
{
cin>>w[i]>>t[i];
for(int j=m;j>=w[i];--j)
{
for(int k=T;k>=t[i];--k)
{
dp[j][k]=max(dp[j][k],dp[j-w[i]][k-t[i]]+1);
}
}
}

例题 1855

依赖背包

言下之意就是存在某些物品 B ,如果要选它 ,那么必须先选另外一个指定的物品 A

发现这样的话无非就要么只买主件,要么就同时买其中若干个附件,但是由于这些情况又不能够同时成立,所以就转化成了一个分组背包的模型。

例题 P 1064

树形背包

顾名思义就是在数上面跑一个背包,以每一个节点为根节点,其子树都是一个独立的背包问题。
然后这个子树算出来的 dp 值作为新的物品价值 来当做这个节点的父节点的待选物,这样对于父节点来说又是一个新的dp问题
例题 P2014 选课

例题 们

BJTU2038

1.分组背包,不一定要用结构体把所有情况构造出来,这启示我们可以直接从状态压缩的角度枚举二进制数来进行考虑
2.但是容量的转移并不是传统的加减,因为回合结束还有一个容量自动增加,所以还有一个增量的过程,要额外做一个映射再进行转移。

ABC383F

很明显的一个背包问题,但是带有限制,也就是所谓的“颜色”。
由于物品的考虑顺序并不会影响最终的答案,所以可以把所有物品都按照颜色排序,这样就保证了 同一种颜色都相邻
这时候定义 dp[i][j][0/1] 为前 i 个达到 j 容量,当前这个颜色选过没有。
注意这个时候转移可能有些不同,比如 dp[i][j][1] 实际上可以是当前这个不选,之前选了一个的情况,所以转移方程可能有点不同。
然后不能开三维,但是直接kick一维似乎也不太行??那就把第一维滚掉吧。

Code

posted @   Hanggoash  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
动态线条
动态线条end
点击右上角即可分享
微信分享提示