首页 > 其他分享 >动态规划

动态规划

时间:2024-10-20 23:34:15浏览次数:1  
标签:背包 int len 序列 动态 规划 我们 dp

对于一个可以用动态规划实现的题目来说我们需要有以下步骤:
1.将原来划分为若干个阶段,每个阶段对应若干个子问题,提取子问题的特征(称为状态)
2.找到每个状态下可能得决策或者是各个状态的转移方式(就是寻找状态转移方程式)
3.按顺序求解每个阶段问题

基础动态规划问题

最长公共子序列

给定一个长度为n的序列a和一个长度为m的序列b,求出一个最长的序列,使得该序列既是A的子序列,也是B的子序列。
1.我们首先寻找这个问题的状态,dp[i][j]就表示序列a的前i个与序列b的前j个的最长公共子序列
2.我们尝试推导状态转移方程式,我们发现当a[i]=b[j]时,我们可以直接可以放在最长公共子序列的末尾,但当不等于时,我们可以选择跳过a[i]或者b[j],此时我们就可以得出状态转移方程式了。
{dp[i][j]=dp[i1][j1]+1 if a[i]=b[j]dp[i][j]=max(dp[i1][j],dp[i][j1] if a[i]b[j]此题的时间复杂度为O(nm)

点击查看代码
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]);
        }
    }
}

最长不下降子序列

给定一个长度为n的序列a,求出一个最长的a的子序列,满足该子序列的后一个元素不小于前一个元素。

算法一

用a数组表示原数组,dp[i]表示前i个数的最长不下降子序列。那么我们就可以遍历第1-i-1个数,如果第j个数不大于它就使dp[i]=dp[j]+1,遍历寻找dp[i]的最大值,此时我们可以得到状态转移方程式。
dp[i]=maxj<i,a[j]<=a[i](dp[j]+1)这种解法的时间复杂度为O(n^2)

点击查看代码
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;
}

算法二

我们发现刚刚的算法是将状态(j,l-1)在合法的前提下转移到(i,l)。所以我们只用设法找到(i,l)使l最大的合法的(i,l)就可以求得最长不下降子序列了。
我们设原数组为a,dp[i]表示所有长度为i的最长不下降子序列的末尾元素的最小值,len表示子序列的长度,所以我们发现当考虑元素a[i]时,若dp[len]小于等于a[i],直接将它插入dp数组的末尾,如果dp[len]大于a[i],我们就可以在dp数组中寻找第一个大于它的元素,然后用a[i]替换。
若直接用暴力查询,那么此题的时间复杂度仍然是O(n^2),但我们发现dp数组是单调的,所以我们可以用二分查找将时间复杂度优化至O(nlogn)。

点击查看代码
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我们是将问题分为左右两个部分,最后合并两个部分的最优值得到全局的最优值。
例题:在一个环上有n个数,进行n-1次合并操作每次操作将相邻的两堆合并成一堆,能获得新的一堆中的石子数量的和的得分。你需要最大化你的得分。
我们先考虑在链的情况,写出状态转移方程。
dp[i][j]=max(dp[i][k]+dp[k+1][j]+r=ijar)
其中求和部分不难发现可以使用前缀和优化。
因为我们要先知道dp[i][k]和dp[k+1][j]的值,因为当中dp[i][k]与dp[k+1][j]中包含的元素数量都小于dp[i][j],所以我们可以使用len=j-i+1作为一个阶段确保我们可以知道前面我们要求的值。
对于一个环,我们可以将数组复制,放在原数组的后面,要使在新的数组取到的长度不超过原数组的长度。

点击查看代码
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的背包能达到的最大总价值,所以我们可以列出状态转移方程式:
dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])
但是我们发现对于一个二维数组来说可能会MLE(内存超限),所以我们考虑一维数组优化。
我们发现对dp[i]有影响的只有dp[i-1],所以我们可以去掉第一维列出如下方程:
dp[j]=max(dp[j],dp[jw[i]]+v[i])
我们在写01代码用一维数组优化时需要注意要从后往前更新,这样才会使得dp[j-[w[i]]的值未被更新,若从前往后遍历的话可能会出现一个物品被取多次的情况(事实上这样正是完全背包的解法)。

点击查看代码
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]);
        }
    }
}

完全背包问题

首先我们考虑朴素的做法,对于当前状态,枚举对每个物体选举多少个,得到状态转移方程式:
dp[i][j]=max(dp[i1][jk×w[i]]+v[i]×k)
但我们可以尝试进行优化,我们发现对于dp[i][j]只用考虑dp[i][j-w[i]]的值,因为dp[i][j-w[i]]已经是之前考虑的最佳取值,因此可以得到新的状态转移方程式:
dp[i][j]=max(dp[i1][jw[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背包进行处理,但是我们可以考虑一种二进制分组优化。假设一个物品有k件,将这个k进行拆分,让他成为n个数之和,使得所有小于等于k的所有数中,都能在那n个数中任选几个数使加和等于那个数的大小。
比如说:
8=1+2+4+118=1+2+4+8+3
不难发现,我们先使2的n次幂从小到大相加(n>=0),如果加上后超出原数那么我们就取要求的数与我们已选取的数的加和的差,不难发现这样的选取就可以满足取到所有小于等于k的取值。

点击查看代码
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;
    }
}

分组背包

有n件物品和一个大小为m的背包,第i个物品的价值为w[i],体积为v[i]。同时,每个物品属于一个组,同组内最多只能选择一个物品。求背包能装载物品的最大总价值。
其实这个问题我们只用对组内进行一个01背包问题就可以解决了。我们可以用t[k][j]来表示第k组第j个的编号。

点击查看代码
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]]);
            }
        }
    }
}
需要注意的是循环的顺序不能更换,才能保证正确性。 参考文献:[OI-wiki-动态规划](https://oi-wiki.org/dp/ "OI-wiki-动态规划")

标签:背包,int,len,序列,动态,规划,我们,dp
From: https://www.cnblogs.com/bssmessi/p/18463301

相关文章

  • 【NowCoder 补全计划】动态规划
    NC21303删除括号给两个合法括号串$s,t$,每次可以从$s$删除一对相邻的括号$()$,问是否可以从$s$变成$t$。设$f(i,j,k)$表示当前原串到$i$,匹配串到$j$,且原串略过$k$个左括号(且这些左括号还未匹配),是否可能成立。如果$k=0$,且$s_i=t_j$,那么$f(i-1,j-1,k)\tof(i,j,......
  • 动态分层强化学习(DHRL)算法
    动态分层强化学习(DHRL)算法详解一、引言在强化学习(ReinforcementLearning,RL)领域,面对复杂、大规模的任务,传统方法往往面临诸多挑战,如高维度状态空间导致的“维数灾难”、长期依赖与稀疏奖励等问题。为了克服这些挑战,分层强化学习(HierarchicalReinforcementLearning,HR......
  • 动态规划的特征
    目录讨论背景分治特点最优子结构自底向上求解子问题复用子问题独立参考文献讨论背景为了方便后文举例,此处叙述一下0-1背包问题的定义:给定一个背包容量W,以及一系列物品{I......
  • Android开发 registerForActivityResult 传值和申请动态权限
    前言  startActivityForResult()被弃用,现在可以通过registerForActivityResult进行Activity之间的传值和获取申请动态权限结果Activity向上传值MainActivitypackagecom.zh.demoimportandroid.content.Intentimportandroid.os.Bundleimportandroid.util.Logimport......
  • Java高级:动态代理
    前言:动态代理是一种设计模式。之所以学习动态代理这种设计模式,是因为后面学习一些技术、项目中,会用到动态代理。一、程序为什么需要代理?代理长什么样?1、为什么需要代理?拿现实举例:一个明星,一开始想唱歌就唱歌、想跳舞就跳舞。等到这个明星稍微有了点热度,就要开始收费表演......
  • Openlayers高级交互(3/20):动态添加 layer 到 layerGroup,并动态删除
    layerGroup是OpenLayers库中的一个类,用于创建图层组。图层组允许您将多个图层组合在一起,并作为一个整体来控制它们的可见性和其他属性。本示例动态添加layer到layerGroup,并动态删除。效果图专栏名称内容介绍Openlayers基础实战(72篇)专栏提供73篇文章,为小白群体提供基......
  • JMeter 动态参数赋值实践
    目录前言单线程+用户参数场景说明实战结果配置明细单线程+CSVDataSetConfig场景说明实践结果配置明细多线程循环单次执行场景说明实践结果配置明细单线程+控制器+用户自定义变量+用户参数场景说明实战结果配置明细多并发+多接口+同步定时器......
  • YOLO11-pose关键点检测:可变形双级路由注意力(DBRA),魔改动态稀疏注意力的双层路由方法BiL
    ......
  • AOP - 自己写 JDK 动态代理增强 bean
    AOP的原理就是给目标对象创建代理对象,达到增强目标对象方法的目的如果目标对象实现了接口就是用JDK动态代理,如果没实现接口就是用三方的CGLIB代理如果不使用AOP想要增强一个bean可以这样做:@ComponentpublicclassTestimplementsBeanPostProcessor,ApplicationCon......
  • 纸币问题(动态规划)
    前言本蒟蒻今天在洛谷上练动态规划,遂写此篇一、纸币问题1P2842纸币问题1题目描述某国有\(n\)种纸币,每种纸币面额为\(a_i\)并且有无限张,现在要凑出\(w\)的金额,试问最少用多少张纸币可以凑出来?输入格式第一行两个整数\(n,w\),分别表示纸币的种数和要凑出的金额。......