CF650D Zip-line

CF650D Zip-line

大概题面:

给定一个长度为 \(n\) 的序列以及$m$个操作,每个操作形如“ \(a_i,b_i\) ”,表示将序列中第 \(a_i\) 个数改为 \(b_i\) .对于每个操作,求出序列的最长严格上升子序列长度。
注意:每个操作之间彼此独立。(即每次操作未进行时的序列是输入时的原序列,而不是上一次操作后得到的序列)
\(1 \leq n,m \leq 400,000\),元素与$b_i \leq 10^9$

每次操作会修改一个数,每次要求LIS (求LIS

暴力做法每次都做修改,重新求一次LIS,复杂度 \(O(n^2logn)\)

考虑每次修改会对答案造成什么影响。设 \(f_i\) 为以 \(i\) 结尾的LIS,设 \(g_i\) 为以 \(i\) 开头的LIS
那么修改前的LIS是 \(ans1=max(f_i+g_i-1)\) (或者说就是 \(ans1=max(f_i)\)
再预处理出修改后每个询问的左右两边的 \(f_i’\)\(g_i’\)

可能在修改后,恰好能将两侧的答案接起来,并让答案更长,此时询问的答案为 \(f_i'+g_i'+1\)
否则要判断在修改了当前节点后有没有影响。如果当前位置一定在原来的LIS上,则答案为 \(ans1-1\) ,否则为 \(ans1\)

*如何判断是否在LIS的必经点上?
首先,如果$i$在LIS上,则$i$一定在LIS的第$f_i$位。(因为$f_i$极大)
所以如果在其他所有满足 \(f_j+g_j-1=ans1\) 的$j$中,不存在 \(f_i=f_j\) ,那么它一定在LIS的必经点上。(如果存在 \(f_i=f_j\) ,则当前这一位$i$可以被替换成$j$,不对答案有影响)

所以可以用二分+辅助数组求出 \(f_i\)\(g_i\)\(f_i’\)\(g_i’\) ,开桶统计 \(f_i\) 和出现的位置,判断一下即可。

时间 \(O(nlogn)\) ,可以通过。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=400005;
const int inf=0x3f3f3f3f;
inline int read(){
    int f=1,x=0;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^48);
        c=getchar();
    }
    return f*x;
}
int n,q;
int a[N];
struct node{
    int x,val,id;
    int f,g;  //在询问点左右两边的最大f, g
    bool operator<(const node& p)const{
        return x<p.x;
    }
}op[N];
int f[N],g[N];
int fx[N];
int ans1;
int tong[N],pos[N];
int ans[N];
inline void work1(){  //求出f,g
    for(int i=1;i<=n;i++)fx[i]=inf;
    for(int i=1;i<=n;i++){
        int l=1,r=n;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(a[i]>fx[mid])l=mid;
            else r=mid-1;
        }
        if(a[i]>fx[l])f[i]=l+1;
        else f[i]=1;
        fx[f[i]]=min(fx[f[i]],a[i]);
    }
    for(int i=1;i<=n;i++)fx[i]=0;
    for(int i=n;i>=1;i--){  //相当于从右往左下降了
        int l=1,r=n;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(a[i]<fx[mid])l=mid;
            else r=mid-1;
        }
        if(a[i]<fx[l])g[i]=l+1;
        else g[i]=1;
        fx[g[i]]=max(fx[g[i]],a[i]);
    }
    for(int i=1;i<=n;i++){
        ans1=max(ans1,f[i]);
    }
}
inline void work2(){  //处理询问
    for(int i=1;i<=n;i++)fx[i]=inf;
    int now=0;
    for(int i=1;i<=q;i++){
        for(int j=now+1;j<=op[i].x-1;j++){  //在询问点左侧最大的f
            fx[f[j]]=min(fx[f[j]],a[j]);
        }
        int l=1,r=n;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(op[i].val>fx[mid])l=mid;
            else r=mid-1;
        }
        if(op[i].val>fx[l])op[i].f=l;
        else op[i].f=0;
        now=op[i].x-1;
    }
    for(int i=1;i<=n;i++)fx[i]=0;
    now=n+1;
    for(int i=q;i>=1;i--){
        for(int j=now-1;j>=op[i].x+1;j--){  //在询问点右侧最大的g
            fx[g[j]]=max(fx[g[j]],a[j]);
        }
        int l=1,r=n;
        while(l<r){
            int mid=(l+r+1)>>1;
            if(op[i].val<fx[mid])l=mid;
            else r=mid-1;
        }
        if(op[i].val<fx[l])op[i].g=l;
        else op[i].g=0;
        now=op[i].x+1;
    }
    for(int i=1;i<=n;i++){
        if(f[i]+g[i]-1!=ans1)continue;
        tong[f[i]]++;
        if(tong[f[i]]==1)pos[f[i]]=i;  //f[i]出现一次要记录
        else pos[f[i]]=0;
    }
}
int main(){
    n=read(),q=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=q;i++){
        op[i].x=read(),op[i].val=read();
        op[i].id=i;
    }
    sort(op+1,op+1+q);
    work1();
    work2();
    for(int i=1;i<=q;i++){
        int x=op[i].x,id=op[i].id;
        int f1=op[i].f,g1=op[i].g;
        if(pos[f[x]]==x)ans[id]=max(ans1-1,f1+g1+1);  //是关键位置就是ans1-1
        else ans[id]=max(ans1,f1+g1+1);
    }
    for(int i=1;i<=q;i++)printf("%d\n",ans[i]);
    return 0;
}

这份代码在CodeForces上 \(624ms\) 通过。

posted @ 2024-08-13 09:48  TanHaoren  阅读(10)  评论(0编辑  收藏  举报