线性dp+背包问题题解
线性dp理解
递推/记忆化搜索,有很多种理解方式
递归重叠子问题的记忆化搜索:
像这里例如
我们从此引出dp第一个性质:最优子结构
大问题的最优解包含小问题的最优解,并且小问题的最优解可以推导出大问题的最优解
递推:
我们不管dp[i-1]是多少,但可以从dp[i-1]推导得出dp[i]
dp第二个性质:无后效性
未来与过去无关
dp实现方法
自顶而下:大问题拆解成小问题求解
常用递归+记忆化实现
自底而上:小问题组合成大问题求解
常用制表递推实现
这是最常见的dp方法
背包dp
0/1背包
状态设计
转移方程
方式1:
方式2:
区别:
方式一是通过上一维来推导这一维,方式二是通过这一维推导下一维
滚动数组
因为看到
交替滚动
用
int dp[2][N];
int now=0,old=1;
for(int i=1;i<=n;i++){
swap(now,old);
for(int j=0;j<=C;j++){
if(j>=w[i]) dp[now][j]=max(dp[old][j],dp[old][j-w[i]]+c[i]);
else dp[now][j]=dp[old][j];
}
}
自我滚动
int dp[N];
for(int i=1;i<=n;i++){
for(int j=C;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
}
}
注:j应该反过来循环,因为 要保证
规律:只要这一位( i 这一位)修改的地方已经用过了,不会再用了,就对答案无影响
完全背包
直接转移就转移完了,注意和0/1背包的区别
0/1背包要从
分组背包
把物品分为n组,每组只能选一个
只要每组枚举选哪个,0/1背包哪组选或不选就完了
多重背包
规定每种物品有
暴力解法
把每个物品看成一个独立的物品进行0/1背包
二进制优化多重背包
将
在这组例子中,相当于用
为什么可以拆物品呢?
我们先看暴力的思路,就是把
单调队列优化多重背包
详见单调队列一章
最长公共子序列
设
当
当
复杂度
最长上升子序列
设
对于每一个
这个过程可以用二分加速,复杂度
P2758 编辑距离
子问题:将
状态设计:
状转方程:
当
当
最小划分
给一个正整数组,把它分为s1,s2两部分,然后求最小的
考虑一部分划分越接近
ybt题解
背包问题
T2:
考虑首先b中的元素a中一定有,其次b中的元素不能被a中的元素拼成
所以我们先把b数组排个序,然后完全背包转移此数能否被之前数拼成即可
T3:
多重背包二进制优化板子题
T4:
多重背包,但是状态只有0/1之分
所以我们考虑可以用一种新奇的方式转移
我们用
存一个
考虑多重背包为什么不能这样做呢?
猜想:可能是因为多重背包的状态维护的最大价值并不具有转移性质,可能有一个较大的不是用
问题暂留,等待各位大佬解答
2025.1.22补档:大佬竟是我自己,我们这种转移方式基于一个原则,就是如果当前位置上dp值已经为1,就不需要转移了,只有连续转移k次才会触发上界限制,然而多重背包无论这一位是否有dp值,我们都需要进行更新,所以不可行
T5:
注意到每个主件最多有两个附件(首先我没有注意到),并且主件必须先选,考虑选主件的过程相当于是一个0/1背包,而连带的附件又很少
所以将主件和附件依次搭配,作为4种情况,在背包时分讨即可
T6:
板子题,先进行一个0/1背包再进行完全背包即可
T7:
板子题
T8:
比较坑人的一题,首先考虑你的约束条件可以构成几个环,然后我们要求有多少种方案能使得开锁的箱子覆盖所有的环
我们设
考虑dp,我们枚举i,然后再枚举j,最后再枚举k表示覆盖i个环开k个箱子,然后转移即可
因为问的是方案,所以要乘一个组合数
注:题目特别坑的地方
考虑组合数,n=300一定爆了,开long long也不行
所以题解中用double存的
唐老师解释说是因为double存的只是一个不精确的数,类似于科学计数法的那种,只存前几位对答案影响大的几位,因为保留4位小数,所以靠后的整数位对答案没有精度影响
2025.1.22补档:
由于我第二次复习时还是没有做出来这道题,并且自信的打开了我的题解
然后发现没看懂,然后再次打开一本通题解,竟然看懂了,遂崩溃,决定重构一篇题解
首先我们发现一个性质
对于每个盒子而言有且仅有一把钥匙能打开锁着它的锁
所以这其实形成了几个环,我们只要拥有了环中的某一个箱子,就可以把整个环中的箱子都打开
然后求概率的话,我们知道总方案数用组合数求,然后再求出来合法的方案数,相除即可
如何求合法的方案数,我们设
记第
T9:
经典dp套路多一个状态就多加一维
设
首先考虑没有必选状态的转移,直接分类讨论,状转式子复杂,代码中有
然后如果有必选状态应该怎么办呢,我们直接截胡,相当于只有选择必选状态的状态才能从这一层i过去,具体实现就是先把所有状态设为-inf,然后能转移的就能更新,否则就不能
题解中的实现是将两种选或不选混在一起写,我选择先把状态必选的刨出来先进行转移用g1存储,不必选的存在g2
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=305,inf=1e9+5;
int v1,v2,n,cnt1,cnt2;
int dp[N][505][55][2];
struct gift{
int p,v;
}g1[N],g2[N];
int main(){
scanf("%d%d%d",&v1,&v2,&n);
for(int i=1;i<=n;i++){
int p,v,s;
scanf("%d%d%d",&p,&v,&s);
if(s) g1[++cnt1]={p,v};
else g2[++cnt2]={p,v};
}
for(int i=1;i<=cnt1;i++){
for(int j1=0;j1<=v1;j1++){
for(int j2=0;j2<=v2;j2++){
dp[i][j1][j2][1]=dp[i][j1][j2][0]=-inf;
dp[i][j1][j2][1]=max(dp[i-1][j1][j2][0]+g1[i].v,dp[i][j1][j2][1]);
if(j1>=g1[i].p){
dp[i][j1][j2][1]=max(dp[i-1][j1-g1[i].p][j2][1]+g1[i].v,dp[i][j1][j2][1]);
dp[i][j1][j2][0]=max(dp[i-1][j1-g1[i].p][j2][0]+g1[i].v,dp[i][j1][j2][0]);
}
if(j2>=g1[i].p){
dp[i][j1][j2][1]=max(dp[i-1][j1][j2-g1[i].p][1]+g1[i].v,dp[i][j1][j2][1]);
dp[i][j1][j2][0]=max(dp[i-1][j1][j2-g1[i].p][0]+g1[i].v,dp[i][j1][j2][0]);
}
}
}
}
// printf("%d\n",dp[cnt1][v1][v2][1]);
for(int i=1;i<=cnt2;i++){
for(int j1=0;j1<=v1;j1++){
for(int j2=0;j2<=v2;j2++){
dp[i+cnt1][j1][j2][0]=dp[i-1+cnt1][j1][j2][0];
dp[i+cnt1][j1][j2][1]=dp[i-1+cnt1][j1][j2][1];
dp[i+cnt1][j1][j2][1]=max(dp[i-1+cnt1][j1][j2][0]+g2[i].v,dp[i+cnt1][j1][j2][1]);
if(j1>=g2[i].p){
dp[i+cnt1][j1][j2][1]=max(dp[i-1+cnt1][j1-g2[i].p][j2][1]+g2[i].v,dp[i+cnt1][j1][j2][1]);
dp[i+cnt1][j1][j2][0]=max(dp[i-1+cnt1][j1-g2[i].p][j2][0]+g2[i].v,dp[i+cnt1][j1][j2][0]);
}
if(j2>=g2[i].p){
dp[i+cnt1][j1][j2][1]=max(dp[i-1+cnt1][j1][j2-g2[i].p][1]+g2[i].v,dp[i+cnt1][j1][j2][1]);
dp[i+cnt1][j1][j2][0]=max(dp[i-1+cnt1][j1][j2-g2[i].p][0]+g2[i].v,dp[i+cnt1][j1][j2][0]);
}
}
}
}
if(dp[n][v1][v2][1]<0) printf("-1");
else printf("%d",dp[n][v1][v2][1]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探