最长子序列,是一种典型的线性dp问题。
最长公共子序列
最长公共子序列(LCS)是一个在一个序列集合中(通常为两个序列)用来查找所有序列中最长子序列的问题。一个数列,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则称为已知序列的最长公共子序列。
基本思想
根据定义发现,最长公共子序列不严格要求两个序列公共部分连续,只要其出现顺序一致即可,考虑分类讨论。
将S1表示为a[N],S2表示为b[N],LCS表示为dp[N][N],解题思路如下:
1.两个序列的最后一个元素相等
那么两个序列的LCS即为S1减去最后一个元素,与S2减去最后一个元素的LCS加上共同的最后一个元素。得到状态转移方程:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
1
]
+
1
)
dp[i][j] = max(dp[i][j],dp[i-1][j-1]+1)
dp[i][j]=max(dp[i][j],dp[i−1][j−1]+1)
2.两个序列的最后一个元素不等
令S1减去最后一个元素,与S2的LCS为L1,S2减去最后一个元素,与S1的LCS为L2,
则两序列的LCS就等于max(L1,L2)。得到状态转移方程:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
−
1
]
,
d
p
[
i
−
1
]
[
j
]
)
dp[i][j] = max(dp[i][j-1],dp[i-1][j])
dp[i][j]=max(dp[i][j−1],dp[i−1][j])
朴素版
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int a[N],b[N],dp[N][N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
if(a[i]==b[j]) dp[i][j] = max(dp[i][j],dp[i-1][j-1]+1);
}
}
cout<<dp[n][n];
}
最长上升子序列
最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。
朴素版
朴素版解法的思路很简单,设置一个dp数组,循环每一个数之前的所有数字。
如果数字比自己小,当前位置的dp就取max(当前位置子序列长度,该数字子序列长度+1,即加上它本身长度),最后再循环找出dp数组的最大值。
#include<iostream>
using namespace std;
const int N = 5e3+5;
long long arr[N],dp[N];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>arr[i];//在线处理
dp[i] = 1;
for(int j=1;j<=i;j++)
{
if(arr[i]>arr[j]) dp[i] = max(dp[i],dp[j]+1);
}
}
int ans = 0;
for(int i=1;i<=n;i++)
{
if(ans<dp[i]) ans = dp[i];
}
cout<<ans;
}
单调栈优化
上文介绍的解法确实易读,但比赛的题目数据可是很恶毒,朴素版解法很容易超时。那么,介绍一下单调栈优化。
优化原理
将可转移的状态存入一个单调队列,并不断对其进行维护+状态转移。
本质: 如果你的队友比你年轻比你强,那你就可以退役了
这里的单调栈不需要出,只需要不断判断加入,就可以写成栈的形式。
每次加入一个数时,判断它是否比栈顶的数大:
是, 直接入栈
否, 二分查找lower_bound,替换掉那个位置的数。
因为前面的数越大,后面的子序列的长度就会越长。
注:求出的最长子序列长度是正确的,但栈内不一定就是那个长度最大的最长上升子序列。
改一改就变成最长下降子序列啦!
模板代码
#include<iostream>
using namespace std;
const int N = 1e5+5;
int arr[N],stk[N];
//stk[i] 存长度为i的上升子序列的最小结尾
int lower_bound(int stk[],int l,int r,int x)
{
while(l < r)
{
int mid = l+r >> 1;
if(stk[mid] >= x)
{
r = mid;
}
else
{
l = mid+1;
}
}
return l;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>arr[i];
}
int j = 0;
for(int i = 1;i <= n;i++)
{
if(!j || arr[i] > stk[j]) stk[++j] = arr[i];
else
{
int l = lower_bound(stk, 1, j, arr[i]);
stk[l] = arr[i];
}
}
cout<<j;
}