浅谈最长上升子序列(LIS)

一、瞎扯的内容

给一个长度为n的序列,求它的最长上升子序列(LIS)

简单的dp

n=read();
for(i=1;i<=n;i++) a[i]=read();
for(i=1;i<=n;i++)
    for(j=1;j<i;j++)
        if(a[j]<a[i])
               dp[i]=max(dp[i],dp[j]+1);
printf("%d",dp[n]);

 

然后发现

 

 

看来需要一个nlogn求LIS的算法

 

二、不瞎扯的内容

上一个算法慢在哪里呢?内层的循环

如果把它变成二分查找不就是nlogn的算法了吗

为此需要进行一下改动

dp数组改为存储长度为i的上升子序列中最小的末尾数字

这样每当外层循环到了一个新的数字

a[i]>dp[len]//新的数字比当前LIS的末尾数字大

dp[++len]=a[i]//愉快地把这个数字接到后面

如果它小于等于dp[len]?

举个例子

序列2 3 1

i=1:找到2,当前LIS={2},没毛病;

i=2:找到3,发现它比LIS的最后一个数字大,把它接到后面,当前LIS={2,3};

i=3:找到1,发现它比3小,那么在当前的LIS中找到一个最小的比1大的数,也就是2,并把2替换成1,当前LIS={1,3}

Q1:为什么可以这么做呢?

我们现在把2替换成了1,如果接下来还能把3替换,我们就可以得到一个更优的LIS

为什么更优?首先LIS是保证尽可能长的,在此基础上,末尾数字越小越优

Q2:如何在当前的LIS中找到一个最小的比a[i]大的数?

根据“长度一定时,末尾数字越小结果越优”原则,dp数组一定是递增的

这个递增就不做证明了,应该比较好想

所以就可以用二分查找了

 

问题解决

for(i=1;i<=n;i++) dp[i]=0x7fffffff;
dp[1]=a[1];
    int len=1,l,r,mid;
    for(i=2;i<=n;i++)
    {
        if(a[i]>dp[len]) dp[++len]=a[i];
        else
        {
            l=0;r=len;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(dp[mid]>a[i]) r=mid-1;
                else l=mid+1;
            }
            dp[l]=min(dp[l],a[i]);
        }
        
    }

 

三、来道题

洛谷P1439

题目简述:给出两个1~n的序列,求他们的最长公共子序列(n≤100000)

这跟LIS有什么关系呢?

还真有关系

不妨改为在第二个序列中匹配第一个序列,最多匹配多少

由于两个序列都是1~n的,可将第一个序列重新定义一下

比如 3 2 1 4 5

3----1;2----2;1----3;4----4;5----5(对应到它是第几个)

那么第二个序列 1 2 3 4 5就会变成3 2 1 4 5

这时再求最长公共子序列的答案是一样的

然而可以发现第一个序列已经变成了1~n的升序排列,那么第二个序列的子序列 也是第一个序列的子序列的充要条件 是 它是一个上升的序列

那么问题就变成了求第二个序列的LIS

用上述方法可以解决

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

inline int read()
{
    int f=1,x=0;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}

int n;
int a[100005],b[100005],f[100005],g[100005];

int main()
{
    int i;
    n=read();
    for(i=1;i<=n;i++)
    {
        a[i]=read();
        g[a[i]]=i;
    }
    for(i=1;i<=n;i++)
    {
        b[i]=read();
        b[i]=g[b[i]];
        f[i]=0x7fffffff;
    }
    f[1]=b[1];
    int len=1,l,r,mid;
    for(i=2;i<=n;i++)
    {
        if(b[i]>f[len]) f[++len]=b[i];
        else
        {
            l=0;r=len;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(f[mid]>b[i]) r=mid-1;
                else l=mid+1;
            }
            f[l]=min(f[l],b[i]);
        }
    }
    printf("%d\n",len);
    return 0;
}

 

~祝大家学习信息学顺利~

BJOI 加油!

posted @ 2019-04-04 17:17  白驹过隙----青春绿  Views(214)  Comments(0Edit  收藏  举报