【海亮DAY22(一)】线性动态规划


专辑:海量集训-动态规划


分三次更新


更新提示:第一次更新


引进一些概念:


###基本概念:动态规划(dynamic programming)是运筹学的一个分支,是求解多阶段决策过程最优化的数学方法。(十分抽象)
用图表示:
这里写图片描述
(呕心沥血的花了我五分钟的事件啊!)

由上图可知,想要实现动态规划,就必须有以下阶段:

1、划分阶段
按照时间或空间特征,有序的或者是可排序的
2 确定状态和状态变量
可以表示各个阶段时所处于的各种客观情况,满足无后效性原则
3 确定决策并写出状态转移方程
根据相邻两段的各个状态之间的关系
4 寻找边界条件
起始条件,终止条件

并且动态规划必须满足以下性质:
  1、最优化原理
  2、无后效性
简单的说这两条原理就是后一种状态只能由前一种状态更新,而不能由后一种状态更新前一种状态,否则就会造成一个环,做很多无用功

那么接下来进入正题:
概念:所谓线性dp(又叫序列dp)就是在一个给定的序列上进行dp,而因为序列大多都是线性,所以称为线性dp
它的状态是可以是一维的,也可以是多维的。本类的状态是基础的基础,大部分的动态规划都要用到它。


一些基本问题:


一、最长上升子序列(LIS)
      题目描述:给定一个序列,请求出其中严格递增的子序列的长度。
分析:首先我们用一个f[i]表示1到i的最长上升子序列的长度。
我们思考,如何进行状态转移?
对于当前状态f[i],可以由之前的状态f[j]转移过来,且我们知道,题目所求的是最长上升序列,我们枚举之前的j时,一旦a[i]>a[j],说明之前的a[j]能与a[i]组成一个最长上升序列,即这时f[i]=f[j]+1。这样,我们只需要求出f[j]的最大值便可。

状态转移方程如下:
f [ i ] = { m a x ( f [ j ] + 1 ) , a[j]&lt;a[i],j&lt;i f [ i ] f[i] = \begin{cases} max(f[j]+1), &amp; \text{a[j]&lt;a[i],j&lt;i} \\[2ex] f[i] \end{cases} f[i]=max(f[j]+1),f[i]a[j]<a[i],j<i


二、最长公共子串(LCS)

题目描述:给定两个字符串A, B,长度分别为n, m (n, m ≤ 1000),求两个字符
串最长的公共子串。

分析:我们设置f [i][j] 表示匹配到字符串A 的第i 位,字符串B 的第j 位得到的最长公共子串。那么对于每一种状态,我们都有三种情况可以选择:
当A[i]==B[j]时:f[i][j]=f[i-1][j-1]+1
当A[i]!=B[j]时:f[i][j]=max(f[i-1][j],f[i][j-1]);

可以得到以下状态转移方程:
f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] + 1 , A[i]==B[j] m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) , A[i]!=B[j] f[i][j]= \begin{cases} f[i-1][j-1]+1, &amp; \text{A[i]==B[j]} \\ max(f[i-1][j],f[i][j-1]), &amp; \text{A[i]!=B[j]} \end{cases} f[i][j]={f[i1][j1]+1,max(f[i1][j],f[i][j1]),A[i]==B[j]A[i]!=B[j]


来几个例题:

一、轮船问题

题目描述:某国家被一条河划分为南北两部分,在南岸和北岸总共有N对城市,每一城市在对岸都有一个城市作为友好城市。每一对友好城市都希望有一条航线来往,于是他们向政府提出了申请。

由于河终年有雾。政府决定允许开通的航线就互不交叉(如果两条航线交叉,将有很大机会撞船)。兴建哪些航线以使在安全条件下有最多航线可以被开通。

分析:针对这道题我们先尝试着理解下图:

这里写图片描述

在下图中,最多的航线是第一、三、四、六条航线。
我们可以得知:当河岸的一边已经有序,最多的航线个数其实就是另一边的最长上升序列个数,就引用了基本问题一
为什么呢?因为当一边有序是,另一边如果是最大上升序列,那么一定不会有交叉,他们只会往同一个方向倾斜(对着上图尝试理解)

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
struct node{
    int l;
    int r;
}a[100001]={};//结构体,方便排序
int x,y,n;
int dp[1000001]={};
int maxx=0;
inline bool mycmp(node xx,node yy){
    return xx.l<yy.l;
}//按左端点从小到大排序
int main(){
    scanf("%d %d",&x,&y);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d %d",&a[i].l,&a[i].r);
    sort(a+1,a+n+1,mycmp);
    for (int i=1;i<=n;i++){
      dp[i]=1;//别忘初始化
      for (int j=1;j<i;j++)
        if (a[j].r<a[i].r) dp[i]=max(dp[i],dp[j]+1);//即右端点的最长上升序列
      maxx=max(maxx,dp[i]);//比较最大值
    }
    printf("%d",maxx);
    return 0;
}


二、合唱队形
题目描述: N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<…Ti+1>…>TK(1<=i<=K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

#####分析:这道题的算法已经很明显了,仍然是最长上升子序列。基于这个队形的性质,我们知道以中间的一个人为分界点,他左边的同学是一个最长上升序列,他右边的同学是一个最长下降序列。
#####问题又可以转化为两个最长上升序列:从左到右做一遍LIS,在右到左做一遍LIS,最后枚举中间点计算最大值即可。
#####计算最多的同学组成的队列的公式:
m a x x = m a x ( m a x x , d p 1 [ i ] + d p 2 [ i ] − 1 ) ( 1 &lt; = i &lt; = n ) maxx =max(maxx,dp1[i]+dp2[i]-1) (1&lt;=i&lt;=n) maxx=max(maxx,dp1[i]+dp2[i]1)(1<=i<=n)
原式子-1是因为中间点被算了两次

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int n;
int dp1[1000001]={};//正着
int dp2[1000001]={};//倒着
int maxx=0;
int a[1000001]={};
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++){
      for (int j=1;j<i;j++)
        if (a[j]<a[i]&&dp1[j]>dp1[i]) dp1[i]=dp1[j];
      dp1[i]++;
    }//正着
    for (int i=n;i>=1;i--){
      for (int j=n;j>i;j--)
        if (a[j]<a[i]&&dp2[j]>dp2[i]) dp2[i]=dp2[j];
      dp2[i]++;
    }//倒着
    for (int i=1;i<=n;i++) maxx=max(maxx,dp1[i]+dp2[i]-1);//比较最大值
    cout<<n-maxx;//注意,题目问的是去掉多少同学,而不是最多可以组成多长的队列
    return 0;
} 


三、导弹拦截
题意简述:某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭,由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹的枚数和导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,每个数据之间有一个空格),计算这套系统最多能拦截多少导弹?

如果要拦截所有导弹最少要配备多少套这种导弹拦截系统?

分析:这道题有两个小问,第二个小问属于贪心,不在这次的范畴,就不多讲

第一小问问我们至少能拦截多少个导弹,题目中说“以后每一发炮弹都不能高于前一发的高度”,这时一个关键句。这句话进行化简后就是说前一个数不能大于后一个数,原问题就转化为此序列的最长不上升子序列(是包括相等的),只需要在之前的代码中改变一个符号便可,这里就不贴代码了。

至于第二小问,就是用贪心求序列的最长不下降序列的个数,不多叙述


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值