LIS LCS LCIS算法总结

版权声明:All rights reserved. https://blog.csdn.net/qq_42814118/article/details/82466949

前言

Q:标题是什么意思?
A: L I S 指最长上升子序列, L C S 指最长公共子序列, L C I S 指最长上升公共子序列。
不清楚上面几个定义的建议复习一下“子序列”之类的概念。这里的上升都是严格的。
这几种经常会在OI竞赛中遇到,属于基本功吧。搞个总结。

LIS

例1

给定一个长度为 n 的数列 a ,求其 L I S 的长度。
n 1000

我们拿个样例,比如:

1  3  6  4  9  7  8

可以看出它的 L I S 是 1 3 4 7 8。
分析可以使用化归法,把 n 的问题化成 n 1 的问题。(类似动规思想)
如果我们知道前面 n 1 个数的 L I S ,怎么求第 n 的?
有人可能会想:如果 a n 能进就放进去不就好了?
但是前面的 n 1 个数可能有多个 L I S ,那要塞哪个呢?
很容易就会想到,肯定是尽量使得 L I S 最后那个数最小的那个啊。
所以我们这样想下去,就是要保证 L I S 中每一位数都是尽可能小的。
拿样例举例,如果当前做到第5位,目前满足上述条件的 L I S 为:

1  3  4  9

加入7,发现7没9大,加不进去。
但是为了使每一位最小,要把9换掉,就变成

1  3  4  7

OK。这样 L I S 的做法已经出来了。
当新加入一个数时,在LIS里从找一个最大的比它小的数,然后与其后面一个数更换。如果这个数在 L I S 尾,那么直接插入。这样是 O ( n 2 ) 的。

例2

同例1。数据范围加大。 n 50000

分析上面解答中的步骤,我们发现“找一个最大的比它小的数”可以二分(由于 L I S 单调),复杂度就优化为 O ( n l o g n ) 了。
代码:

inline int LIS()
{
    lis[len = 1] = a[1];
    for (R int i=2;i<=n;++i)
    {
        if(a[i] > lis[len]) lis[++len]=a[i];
        else
        {
            R int pos=lower_bound(lis+1,lis+len+1,a[i])-lis;
            lis[pos] = a[i];
        } 
    }
    return len;
}

LCS

例3

给定长度分别为 n , m 的两个数列 a b ,求它们的 L C S
n , m 1000

仍然考虑 d p 思想。记 f i , j 为数列 a 做到 i ,数列 b 做到 j L C S 的长度,考虑转移。
如果 a i = b j ,那 f i , j = f i 1 , j 1 + 1
否则, f i , j = m a x ( f i 1 , j , f i , j 1 )

例4

同例3,数据范围扩大。 n m 50000 ,保证相同元素在一个数列中出现的次数不超过20次。

给个样例:

 1  2  3  4  5  6 
 3  6  2  7  3  6
 6  5  2  7  7  3

从上到下分别为序号、数列 a 、数列 b 。通过观察可以发现 L C S 为 6 2 7 3
由于范围过大显然不能用dp的做法了,这里介绍一种 O ( 20 n l o g ( 20 n ) ) 的做法(20的含义见题面)
我们把 a 中每个元素在 b 中出现的位置记下来从大到小,然后和原来在 a 数列中的数替换位置。比如第二行处理后就变成:

(6) (1) (3) (5 4) (6) (1)  

然后对它求一个 L I S ,再替换为对应的数即为 L C S

1  3  5  6
替换后
6  2  7  3

原理:最长公共子序列中的序列序号都是递增的。
代码:

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

#define R register
#define Maxn 1000005
#define Maxnum 1000005

int n,m,s1[Maxn],s2,a[Maxn],lena,lis[Maxn],len;
int app[Maxnum][22],t[Maxnum];
inline int LIS()
{
    lis[len = 1] = a[1];
    for (R int i=2;i<=lena;++i)
    {
        if(a[i] > lis[len]) lis[++len]=a[i];
        else
        {
            R int pos=lower_bound(lis+1,lis+len+1,a[i])-lis;
            lis[pos] = a[i];
        } 
    }
    return len;
}
int main()
{
    freopen("input9.txt","r",stdin);

    scanf("%d",&n);
    for (R int i=1;i<=n;++i) scanf("%d",&s1[i]);

    scanf("%d",&m);
    for (R int i=1;i<=m;++i) 
    {
        scanf("%d",&s2);
        app[s2][++t[s2]] = i;
    }
    for (R int i=1;i<=n;++i)
    {
        sort(app[s1[i]]+1,app[s1[i]]+t[s1[i]]+1);
        for (R int j=t[s1[i]];j;j--) a[++lena]=app[s1[i]][j];
    }
    printf("%d\n",LIS());
    return 0;
}

LCIS

例5

给定长度分别为 n , m 的两个数列 a b ,求它们的 L C I S
n , m 1000

貌似也有人把 L C I S L I C S 来着 …、
仍然dp,类比 L C S f i , j a 做到 i b 做到 j ,并且以 b j 结尾的 L C I S 的长度。
如果 a i = b j ,那么我们要判断一下是否上升,
于是我们就往回找,找到最长的可以接入的来更新,即:
f i , j = m a x ( f i , k ) , k [ 1 , j 1 ] + 1
否则,就直接从之前转移
f i , j = f i 1 , j

The End

猜你喜欢

转载自blog.csdn.net/qq_42814118/article/details/82466949