最长上升子序列LIS

怎么求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(n \log n)\)

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\)
这样储存也避免了用权值线段树/树状数组处理。
当然,你仍然可以离散化后用权值线段树/树状数组处理。做法与这个类似,不写代码了。
可以记录转移方向,找到原来的LIS。
时间是\(O(n \log n)\)

Code:

memset(g,0x3f,sizeof(g));
for(int i=1;i<=n;i++){
    int l=0,r=i-1;
    while(l<r){
        int mid=(l+r+1)/2;
        if(g[mid]<a[i])l=mid;
        else r=mid-1;
    }
    f[i]=l+1;
    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

给定一个长度为 \(n\) 的序列 \(h_1,h_2,h_3,\dots,h_n\) 以及 \(m\) 个操作,每个操作形如 a b,表示将序列中第 \(a\) 个数改为 \(b\)

对于每个操作,求出序列修改后的最长严格上升子序列长度。

注意:每个操作之间彼此独立。即每次操作未进行时的序列是输入时的原序列,而不是上一次操作后得到的序列。

\(1\leqslant n, m \leqslant 4\times 10^5\)

修改数组中的一个数,会造成什么影响?

可能有两种结果,一是 \(a_x\) 不再作为LIS中的一个,二是 \(a_x\) 仍在LIS中。

假如 \(a_x\) 仍在LIS中。可以将要求的序列分为两截,左边是末尾大小小于 \(w\) 的LIS,右边是开头大于 \(w\) 的LIS。可以设 \(f_i\) 表示从左向右的最长上升子序列,\(g_i\) 是从右向左的最长下降子序列。(这样就是以 \(i\) 开头的LIS了)

分别正序和逆序处理 \(f_i\)\(g_i\) 。处理到第 \(x\) 位时,要找到左侧末尾小于 \(w\) 的LIS。可以设 \(fx_i\) 表示LIS长度为 \(i\) 时最小以 \(fx_i\) 结尾。这么设是因为可以发现 \(fx_i\) 是单调不降的。假设处理到当前位置时得到了 \(fx_i\) ,发现之前有个更小的数求出LIS的长度 \(\ge i\) ,那么与当前位置LIS长度为 \(i\) 是矛盾的(毕竟当前位置更大,可以接在那个更小的数后面)。所以在之前有个更小的数LIS一定 \(< i\) ,则 \(fx_i\) 是单调不降的。这时就可以处理到第 \(x\) 位时在 \(fx\) 上二分找到小于 \(w\) 的LIS。逆序也要做相同操作。假设两次求得长度为 \(lenl,lenr\) ,答案就是 \(lenl+lenr+1\)

假如 \(a_x\) 不再作为LIS中的一个。那就需要判断:\(a_x\) 去除后LIS是否会变短?或者说,在原来的LIS中,\(a_x\) 是否必然为其中的一个?设原序列中LIS长度为 \(len\) 。如果 \(f_x+g_x-1=len\) ,就说明 \(a_x\) 可以出现在原来的LIS上。\(a_x\) 一定出现在LIS的第 \(f_x\) 位。那就要统计一下了,有多少个第 \(f_x\) 位?如果只有一个,说明去除 \(a_x\) 后LIS会变短,答案为 \(len-1\) ;否则答案为 \(len\)

将两种可能取最大值即可。

时间复杂度 \(O(N \log N)\)

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
const int inf=0x3f3f3f3f;
int n,T;
int a[N];
struct qurs{
    int x,val,id;
    bool operator<(const qurs& z)const{
        return x<z.x;
    }
}q[N];
int ans[N];
int f[N],g[N];
int fx[N];
int tong[N];
int qur1(int w){
    int l=0,r=n;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(fx[mid]>=w)r=mid-1;
        else l=mid;
    }
    return l;
}
int qur2(int w){
    int l=0,r=n;
    while(l<r){
        int mid=(l+r+1)>>1;
        if(fx[mid]<=w)r=mid-1;
        else l=mid;
    }
    return l;
}
int res1[N],res2[N];
int main(){
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=T;i++){
        scanf("%d%d",&q[i].x,&q[i].val);
        q[i].id=i;
    }
    sort(q+1,q+1+T);
    int now=0,maxn=0;
    for(int i=1;i<=n;i++)fx[i]=inf;
    for(int i=1;i<=n;i++){
        while(now+1<=T&&q[now+1].x<=i){
            now++;
            int val=q[now].val,id=q[now].id;
            res2[id]+=qur1(val);
        }
        f[i]=qur1(a[i])+1;
        fx[f[i]]=a[i];
        maxn=max(maxn,f[i]);
    }
    now=T+1;
    for(int i=1;i<=n;i++)fx[i]=0;
    for(int i=n;i>=1;i--){
        while(now-1>=1&&q[now-1].x>=i){
            now--;
            int val=q[now].val,id=q[now].id;
            res2[id]+=qur2(val);
        }
        g[i]=qur2(a[i])+1;
        fx[g[i]]=a[i];
        if(maxn==f[i]+g[i]-1){
            if(tong[f[i]]==0)tong[f[i]]=i;
            else tong[f[i]]=-1;
        }
    }
    for(int i=1;i<=n;i++){
        if(tong[i]>0)res1[tong[i]]=maxn-1;
    }
    for(int i=1;i<=n;i++){
        if(res1[i]==0)res1[i]=maxn;
    }
    for(int i=1;i<=T;i++){
        int x=q[i].x,id=q[i].id;
        ans[id]=max(res1[x],res2[id]+1);
    }
    for(int i=1;i<=T;i++)printf("%d\n",ans[i]);
    return 0;
}

例#2:P2501 [HAOI2006] 数字序列

有一个长度为 \(n\) 的整数序列 \(a\)

第一行输出一个整数,表示变成一个单调严格上升的序列最少需要改变多少个数。

第二行输出一个整数,表示在改变的数最少的情况下,每个数改变的绝对值之和的最小值。

保证 \(1 \leq n \leq 3.5 \times 10^4\)\(1 \leq a_i \leq 10^5\)。数据保证 \(a_i\) 随机生成。

经典将数组改为LIS最少去掉多少个?

如果是最长不下降子序列,那么答案是 \(n-len\)\(len\) 是最长长度)。

如果是 LIS,令 \(b_i=a_i-i\) ,此时如果 \(b_i\) 是最长不下降子序列,\(a_i\) 就是 LIS 。

第二问,会把中间不在 LIS 中的数字放入中间。假设 \(j,i\) 是可以连接的,那么枚举分界点 \(k\)\(j \sim k\) 都变为 \(b_j\)\(k+1 \sim i\) 都变为 \(b_i\) (这样确实是最优的)。dp 计算即可。提前预处理每个 \(i\) 所连接的 \(j\) 可有效降低复杂度。

虽然是暴力枚举,但 \(n\) 比较小,且数据随机,可以通过。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35005;
int n;
ll a[N];
ll f[N],fx[N];
vector<int>v[N];
ll ans;
ll g[N];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        a[i]+=(n-i);
    }
    for(int i=1;i<=n;i++)fx[i]=1e5+5;
    for(int i=1;i<=n;i++){
        int l=0,r=i-1;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(a[i]>=fx[mid])l=mid;
            else r=mid-1;
        }
        f[i]=l+1;
        fx[l+1]=a[i];
        v[l+1].push_back(i);
        ans=max(ans,f[i]);
    }
    printf("%d\n",n-ans);
    v[0].push_back(0);
    a[n+1]=1e5+5,f[n+1]=ans+1;
    for(int i=1;i<=n+1;i++){
        g[i]=1e18;
        for(auto j:v[f[i]-1]){
            if(j>i||a[j]>a[i])continue;
            ll val=0,res=0;
            for(int k=j+1;k<=i-1;k++){
                val+=abs(a[k]-a[j]);
            }
            res=val;
            for(int k=i-1;k>=j+1;k--){
                val-=abs(a[k]-a[j]);
                val+=abs(a[k]-a[i]);
                res=min(res,val);
            }
            g[i]=min(g[i],g[j]+res);
        }
    }
    printf("%lld",g[n+1]);
    return 0;
}
posted @ 2024-07-23 00:12  TanHaoren  阅读(41)  评论(0)    收藏  举报