写在前面:学DP掌握基础很重要,这里记录一下LIS和LCS,(希望每次在记录时能够收获新的东西
引入问题:
给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。
N有这两个数据范围:
1.
1
≤
N
≤
1000
1≤N≤1000
1≤N≤1000
2.
1
≤
N
≤
100000
1≤N≤100000
1≤N≤100000
样例:
Input:
7
3 1 2 1 8 5 6
output:
4
一.1≤N≤1000
对序列中一个的数a[i]来说,我们定义dp[ i ] 表示以a[i]这个数结尾的最长上升子序列的长度,那么对于下标j∈(1~i-1)这些数来说,如果a[ i ]严格大于a[ j ],就有dp[i] = dp[j]+1,时间复杂度为O(n^2),在这个数据范围内是可以ac的。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int N = 1010;
int n;
int dp[N],w[N];
int main()
{
cin>>n;
for(int i = 1; i<=n; i++) cin>>w[i];
for(int i = 1; i<=n; i++)
{
dp[i] = 1;
for(int j = 1; j<=i-1; j++)
if(w[i]>w[j])
dp[i] = max(dp[i], dp[j]+1);
}
int ans = 0;
for(int i = 1; i<=n; i++) ans = max(ans,dp[i]);
cout<<ans<<endl;
return 0;
}
二.1≤N≤100000
对于这个数据范围来说,上面那种做法显然回超时。那么该如何解决这个问题?
假设我们当前已经走到了第i个数,那么我们肯定想在前i-1个数中拿到最长的那个上升子序列并且可以让a[i]接在后面的,那么我们可以使用一个数组来存储这样一个信息:在前i-1数字中长度为len的子序列的最后一个数字的最小值,为什么是最小值,因为最小值的适用范围更广(如果能接在大的数后面,那么肯定也能接在这个最小值后面),而且,易证,随着len的增加,序列最后的最小值也是严格递增的(反证法可证),那么我们就可以使用二分,找到子序列长度0~len中,最后一个数小于a[ i ]中最大的那一个,然后更新答案。
#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long LL;
const int N = 1e5+100;
int n;
int a[N];
int q[N];
int main()
{
IO;
cin>>n;
for(int i = 1; i<=n; i++) cin>>a[i];
q[0] = -2e9; //哨兵,当前数比前面的数都小
int len = 0;
for(int i = 1; i<=n; i++){
int l = 0, r = len;
while(l<r){
int mid = (l+r+1)>>1;
if(q[mid]<a[i]) l = mid;
else r = mid-1;
}
len = max(len,l+1);
q[l+1] = a[i]; //长度为l+1的子序列的最后一个数更新
}
cout<<len<<endl;
return 0;
}
洛谷P1439.最长公共子序列
给出 1,2,…,n 的两个排列 P1和P2 ,求它们的最长公共子序列。
输入
5
3 2 1 4 5
1 2 3 4 5
输出
3
Hint:对于 100% 的数据,n≤10^5
思路:因为是1到n的全排列,所以P1和P2只是元素顺序不同,可以这样转换:
P1: 3 2 1 4 5
change:a b c d e
P2:1 2 3 4 5
=> c b a d e
经过这样一个转换,P1中的元素全是严格单调递增的,那么对于P2,只要将它的元素都改成change中的对应元素,然后求最长上升子序列,那么就一定是两个序列最长的公共子序列
#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
typedef long long LL;
const int N = 1e5+100;
int n;
int b[N],q[N];
unordered_map<int,int> h;
int main()
{
IO;
cin>>n;
for(int i = 1; i<=n; i++){
int num;
cin>>num;
h[num] = i;
}
for(int i = 1; i<=n; i++){
cin>>b[i];
b[i] = h[b[i]];
}
q[0] = -2e9;
int len = 0;
for(int i = 1; i<=n; i++){
int l = 0, r = len;
while(l<r){
int mid = (l+r+1)>>1;
if(q[mid]<b[i]) l = mid;
else r = mid-1;
}
len = max(len,l+1);
q[l+1] = b[i];
}
cout<<len<<endl;
return 0;
}