LIS LCS LCIS算法总结

前言

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

LIS

例1

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

我们拿个样例,比如:

1  3  6  4  9  7  8

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

1  3  4  9

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

1  3  4  7

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

例2

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

分析上面解答中的步骤,我们发现“找一个最大的比它小的数”可以二分(由于 LIS L I S 单调),复杂度就优化为 O(nlogn) 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 n , m 的两个数列 ab a , b ,求它们的 LCS L C S
n,m1000 n , m ≤ 1000

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

例4

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

给个样例:

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

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

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

然后对它求一个 LIS ,再替换为对应的数即为 LCS 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 n , m 的两个数列 ab a , b ,求它们的 LCIS L C I S
n,m1000 n , m ≤ 1000

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

The End

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值