zoukankan      html  css  js  c++  java
  • 浅谈最长上升子序列(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
    ",len);
        return 0;
    }

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

    BJOI 加油!

  • 相关阅读:
    LintCode 27. 拓扑排序 DFS实现
    LintCode 155. 二叉树的最小深度
    LintCode 90. k数和 II
    LintCode 33. N皇后问题
    Oracle分组后取某列最大值的行数据
    Oracle日期范围
    Mongo可视化工具基本操作
    修改winform安装包写日志文件权限
    Winform安装包出现无法访问网络位置
    ComboBox的真实值和显示值
  • 原文地址:https://www.cnblogs.com/llllllpppppp/p/10655838.html
Copyright © 2011-2022 走看看