欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

LIS——续

程序员文章站 2022-06-06 14:43:11
...

LIS_续

LIS的二分做法

首先来看看一个序列 1 3 5 4 6 8 2

按照之前的滑动窗口的处理思路,你在一个元素入队之前判断他和队顶元素的大小譬如队列是1 3 现在你来一个2,你会发现把2替换3,这样的排列1 2可以给后面的元素提供更大的入度

你单独只放1 3后面假如再来个3就无法入队了,但是1 2的话3就可以进去,这样的话此时的LIS就是最优的,每次我们都从已入队元素中找到第一个大于等于他的元素,然后用当前元素替换他。如果他比队尾元素还大就直接放入队尾

但是要注意 1 3 5 4 6 8 2,假设B数组是储存LIS最小优的数组,len就是LIS

遇到1, B={1} len=1

遇到3, B={1,3} len=2

遇到5, B={1,3,5} len=3

遇到4, B={1,3,4} len=3

遇到6, B={1,3,4,6} len=4

遇到8, B={1,3,4,6,8} len=5

遇到2, B={1,2,4,6,8} len=5可以发现此时的B不是真正的LIS序列,他只是保证单项入度最优,并不影响LIS

LIS——续
luoguP1020

第一问,导弹高度不大于之前的导弹高度,所以就是一个非上升连续子序列,也就是a[i]>=a[j] (i>j)

第二问,一套导弹只能拦截他之后高度比它小的,也就意味着后面比他高的就无法拦截,仔细想想就是求LIS

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+7;
#define  ll long long
int  a[maxn];
int  dp_1[maxn];///LIS
int  dp_2[maxn];///最长非上升子序列
int n=0;
int main()
{
    int x;
    while(scanf("%d",&x)!=EOF)
    {
        a[++n]=x;
    }
    int ans_1=0;
    int ans_2=0;
    memset(dp_1,0,sizeof (dp_1));
    memset(dp_2,0,sizeof (dp_2));
    for (int i=1;i<=n;++i)
    {
        dp_1[i]=1;
        for (int j=1;j<i;++j)///找寻之前的是否可以合并
        {
            if (a[i]>a[j])
            {
                ans_1 = max(ans_1,dp_1[i]=max(dp_1[i],dp_1[j]+1));
            }
            
        }
        ans_1=max(ans_1,dp_1[i]);///判断是否加入原来的子序列,否则直接以a[i]单独创建一个子序列
    }
    for (int i=n;i>=1;--i)///仔细理解后就会发现就是反向跑一遍lis
    {
        dp_2[i]=1;
        for (int j=i+1;j<=n;++j)
        {
           if (a[i]>=a[j])///记得加上等于
            {
               ans_2 = max(ans_2,dp_2[i]=max(dp_2[i],dp_2[j]+1));
            }
        }

        ans_2=max(ans_2,dp_2[i]);
    }
    printf("%d\n%d\n",ans_2,ans_1);
    return 0;
}

O(n^2)的复杂度过了一半

看看二分的

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+7;
const int INF=0x3f3f3f3f;
#define ll long long
int B[maxn];///入队LIS数组
int A[maxn];///原数组
int C[maxn];///最长非上升子序列数组
int n=0;
int A_search(int r,int x){///>
    int mid,l=1;
    while (l<=r){
       mid=(l-r)/2+r;
       if (B[mid]<x)
       {
           l=mid+1;
       }
       else
       {
           r=mid-1;
       }
    }
    return l;///最后满足就是刚好小于
}

int B_search(int r,int x){///>=
    int mid,l=1;
    while (l<=r){
       mid=(l-r)/2+r;
       if (C[mid]<=x)
       {
            l=mid+1;
       }
       else
       {
            r=mid-1;
       }
    }
    return l;
}

int main()
{
    while(EOF!=scanf("%d",&A[++n])){
    }--n;
    int lis=1;
    B[1]=A[1];
    for (int i=2;i<=n;++i){

        if (A[i]>B[lis]){///如果当前比入队的最大值还大就直接入队
            B[++lis]=A[i];
      }
        else {
             int t= A_search(lis,A[i]);///找到第一个大于等于A[i]的元素位置
             B[t]=A[i];///替换
      }
    }
    int ans=1;
    C[1]=A[n];///倒序
    for (int i=n-1;i>=1;--i){///反向查询
          if (A[i]>=C[ans]){///如果当前比入队的最大值还大或者等于就直接入队
            C[++ans]=A[i];

      }
        else {///由于等于的也可以入队,所以查找时直接就查找大于的位置
             int t= B_search(ans,A[i]);///找到第一个大于A[i]的元素位置
             C[t]=A[i];///替换
            // cout<<t<<endl;
      }
    }
    printf("%d\n%d\n",ans,lis);
    return 0;
}

当然你也会发现直接找位置就不必判断了,所以选择lowerbound和upperbound

#include <bits/stdc++.h>
using namespace std;
const int maxn=4e5+7;
const int INF=0x3f3f3f3f;
#define  ll long long
int n=0;
int a[maxn];///储存原数组
int b[maxn];///储存长度
int c[maxn];///储存当前最小子数组LIS
int d[maxn];///储存长度
int f[maxn];///储存当前最小子数组,非上升子序列
int main()
{
     while (scanf("%d",&a[n])!=EOF)
    {
        ++n;
    }
    fill(c,c+n,INF);
    fill(d,d+n,INF);///由于是要直接得到位置,则最后超过实际的查询长度遇到INF就截止了
    int maxx_1=-INF;
    for (int i=0;i<n;++i)
    {
        int j=lower_bound(c,c+n,a[i])-c;///减去本身得到位置
        b[i]=j+1;
        maxx_1=max(b[i],maxx_1);
        c[j]=a[i];///直接取代
    }
    int maxx_2=-INF;
    for (int i=n-1;i>=0;--i)
    {
        int j=upper_bound(d,d+n,a[i])-d;///由于等于号也算进去,此时只要找到最大位置即可
        f[i]=j+1;
        maxx_2=max(f[i],maxx_2);
        d[j]=a[i];
    }
    printf("%d\n%d\n",maxx_2,maxx_1);
    return 0;
}