概念
Loggest Increasing Subsequence:最长上升子序列
Loggest Common Subsequnce: 最长公共子序列(两个或多个序列的问题)
概念就不解释了,,,大家应该都懂
解法
O(nlog(n))的解法
这种解法是贪心+二分的思想。实际并不难,比下面的O(n^2)复杂度的解法还更容易理解。
这种算法的思想大致如下:
现在有 4 2 3 8 9 7 这个序列,下面以他为例,说明求最长子序列的长度。
首先定义一个数组,保存这个最长的上升子序列的每一个元素,设为dp数组。
下面我们就要依次往里面放元素了:
第一个元素是4,直接放进dp。 || 当前dp中元素的情况:4
第二个元素是2,比4小。把dp中的2替换成4.(下面介绍为什么)||当前dp中元素的情况:2
第三个元素是3,比2大,放到dp数组中2的后面 ||当前dp中元素的情况:2 3
第四个元素是8,比3大,放到dp数组中3的后面 ||当前dp中元素的情况:2 3 8
第五个元素是9,比8大,放到dp数组中8的后面 ||当前dp中元素的情况:2 3 8 9
第六个元素是7,替换dp数组中的8 ||当前dp中元素的情况:2 3 7 9
最后结果:最长公共子序列的长度是4.
结论:每次进来一个数 ,他总是替换dp数组从左往右第一个大于它的数。
而存到dp数组中的元素,不一定就是真实的元素。上面的例子的最长上升子序列应该是 2 3 8 9.所以这个数组只是个伪序列,真正有用的是数组的长度。
我现在只能做到这里,证明我也不会。。。。。。。。。但就是这样子。
O(n^2)做法
这种做法,各种数据结构的书上应该都有吧。只要找到状态转移方程:
dp[i]=max(dp[j])+1; //条件是1=<j<i 并且 a[i]>a[j] .
很容易就可以写出代码
LCS转LIS
这便是这篇博客的意义所在了,LIS算法有O(nlogn)的算法,而LCS没有。
看这样一道题:P1439
题目描述
给出 1,2,…,n 的两个排列P1 和 P2 ,求它们的最长公共子序列。
输入格式
第一行是一个数 n。
接下来两行,每行为 n个数,为自然数 1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例
输入 #1复制
5 3 2 1 4 5 1 2 3 4 5
输出 #1复制
3
说明/提示
- 对于50% 的数据, n≤10^3;
- 对于100% 的数据, n≤10^5。
分析:
这道题n达到了10^5,用经典的LCS解法,显然超时。这就需要将LCS向LIS转化
用到的思想:离散化。
举例:如下两个序列
4 2 3 8 9
8 4 2 3 9
做一步离散化处理,不考虑每个元素具体的值,用新数组每个元素表示原数组中元素所在的位置
1 2 3 4 5
4 1 2 3 5
说明:上述第一行表示原序列的4 2 3 8 9的每一个位置,也是就8在原序列第一个序列中的第一位、4在第1位、2在第二位、3在第三位、9在第5位。
---
理解上述的操作之后,下面观察生成的第二个序列的规律:只要求生成的第二个序列的最长上升序列,就可以得到LCS
ok,代码如下,仅供参考
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 5e5+5;
int N;
int a[MAX],b[MAX];
int dp[MAX];
int belong[MAX*100]; //离散化的关键
int main() {
cin >> N;
for (int i = 1; i <= N; ++i) {
cin >> a[i];
belong[a[i]]=i;
}
for (int i = 1; i <= N; ++i) {
cin >> b[i];
b[i]=belong[b[i]];
}
int len = 0;
memset(dp,0,sizeof(dp));
for (int i = 1; i <= N; ++i) {
if(dp[len]<b[i]) {
dp[++len]=b[i];
} else {
*lower_bound(dp+1,dp+len+1,b[i])=b[i]; //二分查找,降低时间复杂度
}
}
cout << len;
}