题意
东东有两个序列A和B。
他想要知道序列A的LIS和序列AB的LCS的长度。
注意,LIS为严格递增的,即a1<a2<…<ak(ai<=1,000,000,000)。
Input
第一行两个数n,m(1<=n<=5,000,1<=m<=5,000)
第二行n个数,表示序列A
第三行m个数,表示序列B
Output
输出一行数据ans1和ans2,分别代表序列A的LIS和序列AB的LCS的长度
Simple Input
5 5
1 3 2 5 4
2 4 3 1 5
Simple Output
3 2
思路
先说LIS,处理有三种方法。分别是O(n^2)的DP,O(nlogn)的二分+贪心法,以及O(nlogn)的树状数组优化的DP,本次我用的是二分加贪心的方法,复杂度少一些。
未使用树状数组优化的DP:
状态:定义fi表示以A;为结尾的最长上升序列的方程。
初始化: f1=1
转移过程fi=max{fi| j<i^Aj< Ai}+1
输出答案: max{f[i], i=1…n}
时间复杂度: 0(n^2)
核心代码:
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j] < a[i])
f[i] = max(f[i], f[j]+1);
我用的二分加贪心的方法,复杂度为O(nlogn)。新建一个 low 数组,low [ i ]表示长度为i的LIS结尾元素的最小值。贪心准则为:对于一个上升子序列,其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护 low 数组,对于每一个a[ i ],如果a[ i ] > low [当前最长的LIS长度],就把 a [ i ]接到当前最长的LIS后面,即low [++当前最长的LIS长度] = a [ i ]。 否则,在low数组中找到第一个大于等于a [ i ]的元素low [ j ],用a[i]去代替这个元素。当然,这个元素的查找如果用遍历的话复杂度还是O(N^2),所以我们用二分法查找。
关于LCS,我用的就是动态规划的方法,复杂度O(nm)。
设计状态:假设f[i][j]为A, A2, … A;和B1, B2, …的LCS长度
初始化:初始f[1][0]= f[0][1] = f[0][0] = 0
转移方程:当Ai== Bj时, f[i][j] = f[i-1][j-1] + 1
否则f[i][j] =max(f[i-1][j], f[i][j-1])
输出答案: f[n][m]
时间复杂度: O(nm)
还有O(mlogn)的方法,就是转化为LIS问题进行处理。记录a中每个元素在b中出现的位置, 再将位置按降序排列。将a中每个元素的位置按a中元素的顺序排列成一个序列c,对c求LIS即可,证明的话太难理解,所以我没用这种方法。
代码
#include<cmath>
#include<stdio.h>
#include <iostream>
#include <algorithm>
#define maxn 5010
#define inf 1e10
#define ll long long
using namespace std;
ll low[maxn],a[maxn],b[maxn];
int f[maxn][maxn];
int n,m,ans1,ans2;
void LIS(){
low[1]=a[1];
ans1=1;
for(int i=2; i<=n; i++)
{
if(a[i]>low[ans1])
low[++ans1]=a[i];
else
low[lower_bound(low,low+ans1,a[i])-low]=a[i];
}
cout<<ans1<<" ";
}
void LCS(){
for (int i=1; i<=n; i++){
for (int j=1; j<=m; j++){
if(a[i]==b[j]) f[i][j]=f[i-1][j-1]+1;
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
ans2=f[n][m];
cout<<ans2<<endl;
}
int main()
{
cin>>n>>m;
for(int i=1; i<=n; i++)
{
cin>>a[i];
low[i]=inf;
}
for(int i=1;i<=m;i++)
{
cin>>b[i];
f[0][i]=0;
f[i][0]=0;
}
f[0][0]=0;
LIS();
LCS();
return 0;
}