最长上升子序列的做法和一些例题

今天听学长讲课学到了新的解决LIS问题的做法。做个记录

1:

考虑 $ O(n^2) $ dp处理。
\(f_i\) 为当前第 \(i\) 个的最长长度,则自然有 $ f_i=\max\limits_{j=1}^{i-1}f_j +1 ,a_i>a_j $

2:

考虑将新加入的序列放入栈中。如果当前的\(a_i\)是上升的,就直接放入栈中。否则在栈中二分,找到第一个大于等于的元素并替换。
要注意,这个做法并不能找到原序列,只能求出长度。
原理是贪心,二分将较小的元素替换,实际操作时会更有机会加入更多数字。如果原序列没有这个元素也不影响,序列的长度是没有改变的。
可以手模样例思考一下。
时间是\(O(nlogn)\)

Code:

for(int i=1;i<=n;i++){
    if(a[i]>s[top])s[++top]=a[i];
    else{
        int l=1,r=top;
        while(l<r){
            int mid=(l+r)>>1;
            if(s[mid]>=a[i])r=mid;
            else l=mid+1;
        }
        s[l]=a[i];
    }
}
printf("%d\n",top);

3:

考虑在#1中的基础上在设立一个g,
\(g_i\)表示已经处理的所有\(f\)中,\(f_j=i\) 中最小的\(a_j\)。也就是\(f\)答案为\(i\)且结尾最小的值。
并且可以发现,\(g_i\)是单调递增的。可以举个反例,如果出现了下降,则一定存在可以更新前面值的情况(长度更长,但结尾值更小)
于是每次处理在\(g\)数组上二分查找,取最大的\(i\)
这样储存也避免了用权值线段树/树状数组处理。
当然,你仍然可以离散化后用权值线段树/树状数组处理。做法与这个类似,不写代码了。
时间是\(O(nlogn)\)

Code:

memset(g,0x3f,sizeof(g));
f[1]=1,g[1]=a[1];
for(int i=2;i<=n;i++){
    int l=1,r=i-1;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(g[mid]<a[i])l=mid;
        else r=mid-1;
    }
    if(g[l]<a[i]){
        f[i]=l+1;
    }
    else f[i]=1;
    g[f[i]]=min(g[f[i]],a[i]);
}
int ans=0;
for(int i=1;i<=n;i++)ans=max(ans,f[i]);
printf("%d\n",ans);

有趣例题1:CF650D Zip-line

我写的:做法

posted @ 2024-07-23 00:12  TanHaoren  阅读(2)  评论(0编辑  收藏  举报