luoguP3402 最长公共子序列(LCS-->LIS)

题目背景

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;
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673477.html