【Codeforces 650D】Zip-line

分治

题目大意:

给定一个数列 $a_1,a_2,a_3,\ldots,a_n$。

每次询问会替换序列的一个数,并问你此时的最长严格上升子序列长度。

询问独立,即每次询问不会实际替换掉那个数。

题解:

记 $f_i$ 表示以 $i$ 结尾的最长严格上升子序列长度,$g_i$ 表示以 $i$ 开头的最长严格上升子序列长度。

求 $f$ 和 $g$ 就是一个普通的 LIS 问题,可以使用单调队列等做法,时间复杂度 $O(n\log n)$。

对于每一个询问 $u,v$,它会修改掉一个数。那么我们考虑两种情况:LIS 中包含这个数,或者 LIS 中不包含这个数。

  • 包含的情况。

    这种情况较好处理。我们只要能求出在 $u$ 之前结尾且结尾的数小于 $v$ 的 LIS 长度 $a$,以及在 $u$ 之后开头且开头的数大于 $v$ 的 LIS 长度 $b$,那么 $a+b+1$ 就是其贡献。

    将询问离线以后,在求 $f$ 和 $g$ 的同时按照一样的方法就可以得到 $a$ 和 $b$。

    时间复杂度 $O(m\log n)$。

  • 不包含的情况。

    令 $h_i$ 表示不包含 $i$ 时原序列的 LIS,那么对于询问 $u,v$,答案只需要对 $h_u$ 取 $\max$ 就好了。

    那么如何求出 $h_i$ 呢?

    对于 $h_i$,我们要找到 $u,v$ 满足 $u\lt i\lt v$,$a_u\lt a_v$,并且 $f_u+g_v$ 最大。

    考虑分治。令当前中点为 $x$。我们考虑对每个在 $[l,x]$ 中的 $f$,找到一个在 $[x+1,r]$ 中的 $g$ 并使结果最大。这样可以更新 $h_{l..x}$。右半边同理可得。

    那么我们可以枚举 $i\in[l,x]$,在 $[x+1,r]$ 中二分出第一个满足 $a_i\lt a_k$ 的 $k$,然后对 $g_{k..r}$ 求最大值 $g_0$。然后 $f_i+g_0$ 的结果可以更新到 $h_{i+1..x}$,这个可以直接更新到 $h_{i+1}$ 上然后求后缀最大值得到。

    需要排序和二分,那么时间复杂度就是 $O(n\log^2 n)$。

    考虑进一步优化。

    首先这个排序可以分治的同时进行归并。

    然后这个二分也可以省。我们按 $a_i$ 从大到小枚举 $i$,那么 $a_k$ 也是不断变小的,双指针维护即可。$g$ 的最值也可以直接维护。

    于是时间复杂度为 $O(n\log n)$。

算法的总时间复杂度为 $O((n+m)\log n)$,空间复杂度为 $O(n+m)$。

Code:

#include<cstdio>
#include<vector>
#include<algorithm>
#include<functional>
using namespace std;
const int N=4e5+10;
int pre[N],suf[N],n,m,a[N],ans[N],res[N];
vector<pair<int,int> >vc[N];
int sta[N],top,cov[N];
pair<int,int>d[N],tmp[N];
void solve(int l,int r){
    if(l==r){
        return;
    }
    const int mid=(l+r)/2;
    solve(l,mid),solve(mid+1,r);
    for(int i=mid,nw=0,it=r;i>=l;--i){
        while(it>mid&&d[i].first<d[it].first)nw=max(nw,suf[d[it--].second]);
        cov[d[i].second+1]=pre[d[i].second]+nw;
    }
    for(int i=l+1;i<=mid;++i)cov[i]=max(cov[i],cov[i-1]);
    for(int i=l;i<=mid;++i)res[i]=max(res[i],cov[i]);
    for(int i=l;i<=mid+1;++i)cov[i]=0;
    for(int i=mid+1,nw=0,it=l;i<=r;++i){
        while(it<=mid&&d[i].first>d[it].first)nw=max(nw,pre[d[it++].second]);
        cov[d[i].second-1]=suf[d[i].second]+nw;
    }
    for(int i=r-1;i>mid;--i)cov[i]=max(cov[i],cov[i+1]);
    for(int i=mid+1;i<=r;++i)res[i]=max(res[i],cov[i]);
    for(int i=mid;i<=r;++i)cov[i]=0;
    int itL=l,itR=mid+1,it=l;
    while(itL<=mid&&itR<=r)
    if(d[itL]<d[itR])tmp[it++]=d[itL++];else tmp[it++]=d[itR++];
    while(itL<=mid)tmp[it++]=d[itL++];
    while(itR<=r)tmp[it++]=d[itR++];
    for(int i=l;i<=r;++i)d[i]=tmp[i];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%d",a+i);
    for(int i=1;i<=m;++i){
        int p,x;
        scanf("%d%d",&p,&x);
        vc[p].emplace_back(x,i);
        ans[i]=1;
    }
    for(int i=1;i<=n;++i){
        res[i]=top;
        for(auto it:vc[i]){
            int v=it.first,id=it.second;
            int len=lower_bound(sta,sta+top+1,v)-sta-1;
            ans[id]+=len;
        }
        int t=lower_bound(sta,sta+top+1,a[i])-sta;
        if(t>top)sta[++top]=a[i];
        else sta[t]=min(sta[t],a[i]);
        pre[i]=t;
    }
    sta[top=0]=1e9+7;
    for(int i=n;i;--i){
        res[i]=max(res[i],top);
        for(auto it:vc[i]){
            int v=it.first,id=it.second;
            int len=lower_bound(sta,sta+top+1,v,greater<int>())-sta-1;
            ans[id]+=len;
        }
        int t=lower_bound(sta,sta+top+1,a[i],greater<int>())-sta;
        if(t>top)sta[++top]=a[i];
        else sta[t]=max(sta[t],a[i]);
        suf[i]=t;
    }
    for(int i=1;i<=n;++i)d[i]=make_pair(a[i],i);
    solve(1,n);
    for(int i=1;i<=n;++i)
    for(auto it:vc[i])ans[it.second]=max(ans[it.second],res[i]);
    for(int i=1;i<=m;++i)printf("%d\n",ans[i]);
    return 0;
}