简单动态规划
对于一个可以用动态规划实现的题目来说我们需要有以下步骤:
1.将原来划分为若干个阶段,每个阶段对应若干个子问题,提取子问题的特征(称为状态).
2.找到每个状态下可能得决策或者是各个状态的转移方式(就是寻找状态转移方程式).
3.按顺序求解每个阶段问题.
基础动态规划问题
最长公共子序列
给定一个长度为
1.我们首先寻找这个问题的状态,
2.我们尝试推导状态转移方程式,我们发现当
此题的时间复杂度为
string a,b; int n,m,dp[N][N]; int solve(){ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } }
最长不下降子序列
给定一个长度为
算法一
用a数组表示原数组,
这种解法的时间复杂度为
int n,a[N],dp[N]; int solve(){ int ans=1; for(int i=1;i<=n;i++){ dp[i]=1; for(int j=1;j<i;j++){ if(a[j]<a[i]) dp[i]=max(dp[j]+1,dp[i]); ans=max(ans,dp[i]); } } return ans; }
算法二
我们发现刚刚的算法是将状态
我们设原数组为
若直接用暴力查询,那么此题的时间复杂度仍然是
int n,a[N],dp[N]; int solve(){ int len=1; dp[len]=a[1]; for(int i=2;i<=n;i++){ //a[i]>=dp[len]<-->a[i]<dp[len],因此使用upper_bound if(a[i]>=dp[len]) dp[++len]=a[i]; else dp[upper_bound(dp+1,dp+1+len,a[i])-dp]=a[i]; } return len; }
对于最长上升子序列,我们同理可以解决.
区间DP
对于区间DP我们是将问题分为左右两个部分,最后合并两个部分的最优值得到全局的最优值.
例题:在一个环上有
我们先考虑在链的情况,写出状态转移方程:
其中求和部分不难发现可以使用前缀和优化.
因为我们要先知道
对于一个环,我们可以将数组复制.放在原数组的后面,要使在新的数组取到的长度不超过原数组的长度.
for(int len=2;len<=n;len++){ for(int l=1;l+len<=2*n){ int r=l+len-1; for(int k=l+1;k<r;k++) dp[l][r]=max(dp[l][k]+dp[k+1][r],dp[l][r]); dp[l][r]+=s[r]-s[l-1];//前缀和优化 } }
背包DP
01背包问题
首先考虑最为简单入门的01背包问题.
有n个物品和一个容量为W的背包,每个物品有重量Wi和价值Vi两种属性,要求选若干物品放入背包使背包中物品的总价值最大且背包中物品的总重量不超过背包的容量.
我们对于一个物品,我们有放入和不放入两个选择,我们定义dp[i][j]为对于前i个物品容量为j的背包能达到的最大总价值,所以我们可以列出状态转移方程式:
但是我们发现对于一个二维数组来说可能会MLE(内存超限),所以我们考虑一维数组优化.
我们发现对
我们在写01代码用一维数组优化时需要注意要从后往前更新,这样才会使得
int n,wmax,w[N],v[N],dp[N]; void solve(){ for(int i=1;i<=n;i++){//考虑每件物品 for(int j=wmax;j>w[i];j++){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } }
完全背包问题
首先我们考虑朴素的做法,对于当前状态,枚举对每个物体选举多少个,得到状态转移方程式:
但我们可以尝试进行优化,我们发现对于
与前面的01背包问题对比,我们也不难发现这次的更新是正向的,可以对于前面取过该物品得到的最佳取值继续进行选取.
int n,wmax,w[N],v[N],dp[N]; void solve(){ for(int i=1;i<=n;i++){//考虑每件物品 for(int j=w[i];j<=wmax;j++){ dp[j]=max(dp[j],dp[j-w[i]]+v[i]); } } }
多重背包
多重背包与01背包的区别就是,对于每个物品,01背包只能取1次,到那时多重背包可以取k次.
对于多重背包,我们可以想到一种十分朴素的办法,就是将k个同种物品转化为k个物品,每个物品只能选一次,用01背包进行处理,但是我们可以考虑一种二进制分组优化.假设一个物品有
比如说:
不难发现,我们先使
struct node{ int v,w; }list[N]; int n,tot; void changenum(){ cin>>n; for(int i=1;i<=n;i++){ int v1,w1,num,c=1; cin>>v1>>w1>>num; while(num>c){ num-=c; list[++tot].w=c*w1; list[tot].v=c*v1; c*=2; } list[++tot].w=num*w1; list[tot].v=num*v1; } }
分组背包
有
其实这个问题我们只用对组内进行一个01背包问题就可以解决了.我们可以用
for(int k=1;k<=ts;k++){//遍历每个背包 for(int i=wmax;i>0;i++){//遍历背包容量 for(int j=1;j<=cnt[k];j++){//遍历每组背包的每个物品 if(w[t[k][j]]<=i){//如果背包容量充足 dp[i]=max(dp[i],dp[i-w[t[k][j]]]+v[t[k][j]]); } } } }
需要注意的是循环的顺序不能更换,才能保证正确性.
本文作者:bssmessi的博客
本文链接:https://www.cnblogs.com/bssmessi/p/18463301
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步