主要包括以下两个方面
一、最长上升下降不上升不下降序列问题:最长上升子序列,最长不下降子序列,最长下降子序列,最长不上升子序列
二、最长公共序列问题:最长公共子序列
一、最长上升下降不上升不下降序列问题
其中关于前四种序列可以参考往期自己写的文章,四种序列可以都理解为dp数组在判断条件的时候条件不同,但是思想是相同的
根本思想就是:
从第一个结果进行遍历,根据当前遍历到的值和之前遍历到的值进行比较,然后得到相对较大的那个
状态转移方程
最长上升子序列和最长不下降子序列(动态规划)_Elephant_King的博客-CSDN博客
对于上升下降不上升不下降子序列再理解_Elephant_King的博客-CSDN博客
模板:
for(int i=0;i<n;i++){
dp[i]=1 //初始化长度
for(int j=0;j<i;j++){
if(v[i]>=v[j]){ //判断条件,四种序列就这个地方不一样
dp[i]=max(dp[j]+1,dp[i]); //状态转移方程
}
}
}
对于序列的基本应用的例题:
1.求最长不下降序列(信息学奥赛一本通-T1259)信息学奥赛一本通(C++版)在线评测系统
就是直接求最长不下降子序列,没有拐弯抹角
但是这里不光只是求序列的长度,而是还要求序列的具体组成元素,在下面会给出求具体元素的方法
2.最长上升子序列(信息学奥赛一本通-T1281)信息学奥赛一本通(C++版)在线评测系统
就是求最长上升子序列的长度
3.怪盗基德的滑翔翼(信息学奥赛一本通-T1286)信息学奥赛一本通(C++版)在线评测系统
这道题的本质是求最长下降子序列的长度,但是这里是求双向的最大长度,既要判断从左向右的方向,也要判断从右向左的方向,如图所示
4. 拦截导弹(信息学奥赛一本通-T1260)信息学奥赛一本通(C++版)在线评测系统
这道题的本质是求最长下降子序列和最长下降子序列的个数
最长下降子序列的个数就是最长不上升子序列的长度,因为最长不上升子序列的每一个元素都代表着一条最长下降子序列中的一个元素
5.友好城市(信息学奥赛一本通-T1263)信息学奥赛一本通(C++版)在线评测系统
这道题本质就是一个元素的位置上有两个数据,先按照第一个数据从小到大排序,然后再求第二个数据的最长上升子序列
6.合唱队形(洛谷-P1091)[NOIP2004 提高组] 合唱队形 - 洛谷
说是求要出列的人的个数,实际上就是再求一个字序列满足先上升在下降的最多元素的个数,需要正着求一遍最长上升子序列,再反着求一遍最长上升子序列
正着反着都要求,然后对应元素的max(dp1+dp2-1)(需要减一因为自己算了两遍)就是最多的人数,然后总人数减去最多的人数就是要出列的人数
7.最大上升子序列和(信息学奥赛一本通-T1285)信息学奥赛一本通(C++版)在线评测系统
这道题也是求最大上升子序列,不过dp数组里面不是存储的当前最长上升子序列的长度,而是当前上升子序列的和的最大值,核心式子相当于
dp[i]=max(dp[i],dp[j]+v[i])
而不是求长度的
dp[i]=max(dp[i],dp[j]+1)
总结用法:
1.直接求序列的长度,如例题2
2.求序列长度与输出这个最长序列,如例题1
两种方法,第一种用栈来实现,损耗的时间可能较长,但是空间复杂度较小
stack<int> s;
for(int i=ans;i>=0;i--){ //从得到最长子序列的位置,从后向前判断
if(dp[i]==mmax){ //找到了对应长度的
s.push(v[i]); //放入栈中
mmax--; //长度减一
}
}
while(!s.empty()){
cout<<s.top()<<" "; //输出
s.pop();
}
第二种,用结构体实现 ,不容易超时,但是空间复杂度较大
struct Node{
int val; //代表元素的大小
int len; //代表到这个位置的子序列的长度是多少
int ans[N]; //代表到这个位置子序列有几个
}dp[N];
//当序列要加1的时候
dp[i]=dp[j]; //先让两个结构体相等
dp[i].ans[++len]=dp[i].val //然后存入新的元素
3.两个方向都要求,如例题3
先正着求再反着求
4.求上升序列(下降序列)最少有多少个能涵盖整个数组,如例题4
求上升子序列的序列个数,就是求最长不下降子序列的元素个数
5.两个数组求互不交叉的个数,如例题5
先对一个进行排序,然后对另一个用刚刚排序的规则进行判断
6.求这个序列先上升后下降或者先下降后上升,如例题6
先正着求一个dp数组,再反着求一个dp数组,对应这个元素的位置i的结果就是dp1[i]+dp2[i]-1,因为i这个元素在两个序列中都算了一次长度,相当于算了两次
7.求最长序列的和最大,如例题7
将原本的dp数组代表最长序列的长度变化为代表序列的和的最值
注意问题:
1.最好在for循环里顺便对dp数组进行初始化,不要用fill,虽然我也不知道为啥会出错,但是他就是会出错
二、最长公共子序列
问题就是给你两个数组,让你求出从两个数组各抽取若干元素,问最长的公共长度是多少
原理看之前自己写的文章:最长公共子序列问题(LCS)_Elephant_King的博客-CSDN博客
状态转移方程
模板:
//不用初始化
for(int i=0;i<a.length();i++){
for(int j=0;j<b.length();j++){
if(a[i]==b[j]) dp[i+1][j+1]=max(dp[i][j]+1,dp[i+1][j+1]);
//相等时的转移方程
else dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
//不相等时的转移方程
}
}
对于序列的基本应用的例题:
1.最长公共子序列(信息学奥赛一本通-T1265)信息学奥赛一本通(C++版)在线评测系统
就是最基本的套公式
2.公共子序列(信息学奥赛一本通-T1297)信息学奥赛一本通(C++版)在线评测系统
也是最基本的套公式
3.回文字符串(51Nod-1092)http://www.51nod.com/Challenge/Problem.html#problemId=1092
这道题本质是求不连续的最长回文子串的个数,做法就是把这个给出的序列复制一份并逆序,然后求两个序列的最长公共子序列的长度,用总长度减去最长公共子序列长度就是需要添加几个元素才可以变成回文子串
4.最长回文子串(LeetCode-5)力扣
这道题属于区间dp的范畴,到时候区间dp会总结
5.最长公共子上升序列(信息学奥赛一本通-T1306)信息学奥赛一本通(C++版)在线评测系统
属于两种情况的总结,先判断是不是上升,如果是上升的话找到之前最长的情况,再判断是不是公共的,如果是公共的就更新,不是公共的就不更新。
本质就是从上升子序列里面挑出几个特殊的来判断
或者是从公共子序列挑出几个是不是上升的
for(int i=1;i<=m;i++){
now.len=0;
memset(now.a,0,sizeof(now.a));
for(int j=1;j<=n;j++){ //向前遍历每个元素
if(a[j]<b[i]&&now.len<dp[j].len){ //如果是上升,更新now
now=dp[j];
}
if(a[j]==b[i]){ //如果相等更新dp
dp[j]=now;
dp[j].a[++dp[j].len]=a[j];
}
}
}
总结
1.最基本的求法
2.回文子串问题
获得一个新的倒序数组,求两个数组的最长公共子序列
3.上升子序列和公共子序列的组合
本质就是从上升子序列里面挑出几个特殊的来判断
或者是从公共子序列挑出几个是不是上升的