题目背景
DJL为了避免成为一只咸鱼,来找Johann学习怎么求最长公共子序列。
题目描述
经过长时间的摸索和练习,DJL终于学会了怎么求LCS。Johann感觉DJL孺子可教,就给他布置了一个课后作业:
给定两个长度分别为n和m的序列,序列中的每个元素都是正整数。保证每个序列中的各个元素互不相同。求这两个序列的最长公共子序列的长度。
DJL最讨厌重复劳动,所以不想做那些做过的题。于是他找你来帮他做作业。
输入输出格式
输入格式:
第一行两个整数n和m,表示两个数列的长度。
第二行一行n个整数a_1,a_2,…,a_n,保证1≤a_i≤〖10〗^9。
第三行一行m个整数b_1,b_2,…,b_m,保证1≤b_i≤〖10〗^9。
输出格式:
一行一个整数,表示两个数列的最长公共子序列的长度。
输入输出样例
输入样例#1:
6 6
1 3 5 7 9 8
3 4 5 6 7 8
输出样例#1:
4
分析:
一眼秒N^2dp
然而面对300000就jj了,
这样我们就要学习新知识了:
最长公共子序列问题(LCS):
给定2个字符串,求其最长公共子串。如abcde和dbada的最长公共字串为bd。
显然dp啊:
一、n^2算法
f[i][j]表示a串前i个和b串前j个的最长公共子串的长度
若A[i]==B[j],f[i][j]=f[i-1][j-1]+1
否则f[i][j]=max(f[i-1][j],f[i][j-1])
二、nlogn算法
f[i][j]仅在A[i]==B[j]处才增加,
对于不相等的地方对最终值是没有影响的
故枚举相等点处可以对其进行优化
则对于f[i][j](这里只计算a[i]==b[j]的i和j),
取最大的f[p][q],满足(p < i,q < j),通过二叉搜索树可以在logn的时间里获取到最大的f[p][q])。
这里也可将其转化为最长递增子序列问题
举例说明:
A:abdba
B:dbaaba
1:先顺序扫描A串,取其在B串的所有位置:
2:a(2,3,5) b(1,4) d(0)
3:A用每个字母的反序列替换,则最终的最长严格递增子序列的长度即为解
最长递增子序列可在O(nlogn)的时间内算出
替换结果:532 41 0 41 532
对于序列53241041532做LIS
最大长度为3.
LIS(最长上升子序列nlogn)
建一个g数组,g[i]表示长度为i的lis中,作为结尾的最小的数,
这样的g是单调增的,
每次考虑以i为结尾的上升子序列时,我们只要在g二分中找到小于a[i]的最大值g[j]
f[i]=j+1
然而在真正写的时候f数组就可以省去了
g[1]=num[1]; //长度为1的最小
//f[1]=1; 写完会发现f可有可无
int t=1; //当前最长上升子序列的长度
for (i=2;i<=n;i++)
{
int l=1,r=t,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (g[mid]<num[i]) //找到小于a[i]的最大的一个
l=mid+1; //mid+1 不知不觉就+1
else r=mid-1;
}
g[l]=num[i]; //l就是长度
//f[i]=l;
if (l>t) t=l;
}
return t; //最长上升子序列长度
回到LCS
简单说明:上面的序列和最长公共子串是等价的
对于一个满足最长严格递增子序列的序列,该序列必对应一个匹配的子串
a串中的每一个数子都是该字符在b中出现的位置
反序是为了在递增子串中,每个字母对应的序列最多只有一个被选出
反证法可知不存在更大的公共子串,因为如果存在,则求得的最长递增子序列不是最长的,矛盾。
不太明白。。。
ps:LCS(最长公共子序列)在最终的时间复杂度上不是严格的O(nlogn),不知均摊上是不是。
举个退化的例子:
A:aaa
B:aaaa
则序列=>321032103210
长度变成了n*m ,最终时间复杂度O(n*m*(lognm)) > O(n*m)。
这种情况不知有没有很好的解决办法
然而
这道题保证每个序列中的各个元素互不相同
这样就可以把A转化成一个n级别的序列,不然的话时间会退化得很厉害
又因为ai<=1e9,这样就需要一个map映射一下,记录每个数对应的位置,当然还有hash做法
//map->
#include<cstdio>
#include<iostream>
#include<cstring>
#include<map>
using namespace std;
const int N=300005;
int n,m,a[N],g[N],num[N];
map<int,int> mp;
/*void doit() //n^2
{
int i,j,ans=0;
for (i=1;i<=n;i++)
for (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]);
}
printf("%d",f[n][m]);
}*/
int LCS()
{
int i,j;
int s=1;
while (!num[s]) num[s]=999999,s++; //如果a[s]==0表示b中没有这个数
g[1]=num[1]; //长度为1的最小
//f[1]=1; 写完会发现f可有可无
int t=1; //当前最长上升子序列的长度
for (i=2;i<=n;i++)
{
int l=1,r=t,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (g[mid]<num[i]) //找到小于a[i]的最大的一个
l=mid+1; //mid+1 不知不觉就+1
else r=mid-1;
}
g[l]=num[i]; //l就是长度
//f[i]=l;
if (l>t) t=l;
}
return t;
}
void doit()
{
int i,j,cnt=0;
for (i=1;i<=n;i++) num[i]=mp[a[i]]; //a中的数字在b中的位置
printf("%d",LCS());
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=m;i++)
{
int u;
scanf("%d",&u);
mp[u]=i; //数字u在b中出现的位置
}
doit();
return 0;
}