Luogu 3402 最长公共子序列(二分,最长递增子序列)

Luogu 3402 最长公共子序列(二分,最长递增子序列)

Description

经过长时间的摸索和练习,DJL终于学会了怎么求LCS。Johann感觉DJL孺子可教,就给他布置了一个课后作业:

给定两个长度分别为n和m的序列,序列中的每个元素都是正整数。保证每个序列中的各个元素互不相同。求这两个序列的最长公共子序列的长度。

DJL最讨厌重复劳动,所以不想做那些做过的题。于是他找你来帮他做作业。

Input

第一行两个整数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$$。

对于40%的数据,n, m≤3000
对于100%的数据,n, m≤300000

Output

一行一个整数,表示两个数列的最长公共子序列的长度。

Sample Input

6 6
1 3 5 7 9 8
3 4 5 6 7 8

Sample Output

4

Http

Luogu:https://www.luogu.org/problem/show?pid=3402

Source

二分

解决思路

这道题的弱化版在这里

因为数据加强了,我们不能使用弱化版中的动态规划方法了。

题目中给出了元素互不相同的条件。我们可以将求最长公共子序列(LCS)转化为求最长递增子序列(LIS)

读入A数组的时候,我们定义一个Map[x]表示数x对应的编号,Map[x]=i;这样就相当于把数组A变成了一个递增的序列。

如果我们把B数组里的数都按照A中的规则转置一下,这个题目就变成求B的最长递增子序列了,具体操作如下:

然后在处理B数组的时候(假设我们现在处理数字x),先让x=Map[x]如果此时x==0则说明原来的数字x并没有在数组A中出现过,所以自然也不会成为最长公共子序列的解,直接舍去即可。

这时我们将找到的最长递增子序列放入一个vector(这里用Arr表示),并保证其有序。若x(这个x是用Map转置后的)比vector里所有元素都大(即比Arr[Arr.size()-1]大),则直接将其放到队尾;否则二分查找第一个比x大的元素并替换之。

相信你也发现了,这个算法的核心思路是贪心,我们可以看到,它是让Arr中的元素尽可能的小,以此来求出最长递增子序列,也就可以求出原题的最长公共子序列。

需要注意的是,用这种算法求最长递增子序列只能求出其长度,Arr中的数不一定就是最后要求的最长递增子序列。(为什么呢?仔细想一想)

PS:这个题目真心坑爹,不仅要用上面的这个算法,读入还必须用getchar()读入优化,就连scanf都会超时,真是……

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;

int n,m;
map<int,int> Map;
vector<int> Arr;

int read();//读入优化,一定要打

int main()
{
    int x;
    n=read();
    m=read();
    for (int i=1;i<=n;i++)
    {
        //scanf("%d",&x);
        Map[read()]=i;
    }
    for (int i=1;i<=m;i++)//将LCS问题转换为LIS问题,降低时间复杂度
    {
        //scanf("%d",&x);
        x=Map[read()];
        if (x==0)
            continue;
        if ((Arr.size()==0)||(x>Arr[Arr.size()-1]))//如果比其中的都大,直接放到后面
            Arr.push_back(x);
        else//否则,替换第一个比它大的
            *lower_bound(Arr.begin(),Arr.end(),x)=x;
    }
    cout<<Arr.size()<<endl;//Arr的大小就是最后最长子序列的大小
    return 0;
}

int read()
{
    int x=0;
    int k=1;
    char ch=getchar();
    while (((ch<'0')||(ch>'9')) &&(ch!='-'))
        ch=getchar();
    if (ch=='-')
    {
        k=-1;
        ch=getchar();
    }
    while ((ch<='9')&&(ch>='0'))
    {
        x=x*10+ch-48;
        ch=getchar();
    }
    return x*k;
}
自己选择的路,跪着也要走完。朋友们,虽然这个世界日益浮躁起来,只要能够为了当时纯粹的梦想和感动坚持努力下去,不管其它人怎么样,我们也能够保持自己的本色走下去。
原文地址:https://www.cnblogs.com/SYCstudio/p/7137870.html