题目内容

P1020

大意:求最长不下降子序列和最长上升子序列。

解题思路

半年前的坑,今天给填上。不难想出 O(n^2) 的做法:设 f_i 为以 a_i 结尾的 LIS 的长度,则有状态转移方程

f_i= \max_{j

每次都往前面扫描一遍,复杂度 O(n^2)

#include <cstdio>
#include <cstring>
#define maxn 100009
int a[maxn],b[maxn],h[maxn];

using namespace std;
int main()
{
    int ans1=0;
    memset(a,0,sizeof(a));
    memset(b,1,sizeof(b));
    memset(h,0x7ffffff,sizeof(h));
    int n=1,m=0;//n:the num of missiles, m:the num of sys
    while(scanf("%d",&a[n])!=EOF)
    {
        int l=0;
        for(int i=n-1;i>0;i--)
        {
            if(a[i]>=a[n]&&b[i]>l)
                l=b[i];
        }
        b[n]=l+1;
        if(b[n]>ans1) ans1=b[n];
        int x=0;
        for(int k=1;k<=n;k++)
        {
            if(h[k]>=a[n])
                if(x==0)
                    x=k;
                else if(h[k]<h[x])
                    x=k;
        }
        if(x==0)
        {
            m++;
            x=m;
        }
        h[x]=a[n];
        n++;
    }
    printf("%d %d\n",ans1,m);
    return 0;
}

当然这个算法太慢了,过不了洛谷的后面 10 个极限数据,因此我们需要 O(n\log n) 的做法,这个算法结合了二分查找和贪心。

如果我们此时设 f_i 表示长度为 i 的 LIS 的结尾的数字的话,思考:是不是当 f_i 越小,才越方便后面的数接进这个 LIS 呢,具体维护 f_i 的方法如下:

a_2 开始枚举每一个 a_i,如果新的 a_i 能接上这个已知最长的 LIS,那就接上去,否则就在 f 数组里面寻找能让 a_i 接上的 LIS 的长度,由于 f 数组一定满足单调递减,所以只需要利用二分查找在里面寻找第一个 f_j 使得 f_j\ge a_i 就可以了,然后更新对应的 f_j 即可。二分查找可以使用 STL 来偷懒降低代码复杂度。

该算法的复杂度为 O(n\log n)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=100000+5;
int f1[maxn],f2[maxn],a[maxn],tot;

bool cmp(const int& a,const int& b)
{
    return a>b;
}

int main()
{
    memset(f1,0x7fffffff,sizeof(f1));
    memset(f2,0x7fffffff,sizeof(f2));
    while(scanf("%d",&a[++tot])!=EOF);
    f1[1]=a[1],f2[1]=a[1];
    //f[i]存储长度为i的序列的尾元素
    int len1=1,len2=1;//len表示查询到的LIS的最长长度
    for(int i=2;i<tot;i++)
    {
        if(a[i]<=f1[len1])//如果当前的a[i]可以接在最长的LIS的末尾
            f1[++len1]=a[i];//就接上去
        else
        {
            int p1=upper_bound(f1+1,f1+1+len1,a[i],cmp) - f1;
            //否则找到第一个大于他的
            f1[p1]=a[i];//然后接在后面,更新最小值
        }
        if(a[i]>f2[len2])
            f2[++len2]=a[i];
        else
        {
            int p2=lower_bound(f2+1,f2+len2+1,a[i])-f2;
            f2[p2]=a[i];
        }
    }
    printf("%d\n%d\n",len1,len2);
    return 0;
}
最后修改日期:2020年3月7日

作者

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。