题解:AT_arc218_d [ARC218D] I like Increasing

· · 题解

更好的阅读体验

我们称序列 p 上的一组 (i, i+1) 满足 p_i < p_{i+1},为一个上升位。题目要求的就是,使上升位数量最大的前提下,最短的子序列长度。

先不考虑区间查询。首先我们要考虑的是,给你一个排列 a,怎么求出它的一个子序列,使得上升位数量最大。

我们可以先得到答案上界:我们将序列 a 分成若干个极长的下降段,如 (4, 1, 5, 3, 7, 6, 2) 我们分成 (4, 1) , (5, 3), (7, 6, 2)。假设分成了 k 段,那么答案上界应该是 k-1

这个为什么是上界很好理解:由于每一段递减,因此每一段内部不可能产生上升位;而相邻两个段至多产生一个上升位。因此总的上升位数量不会超过段数 -1。取到这个上界的条件也就是,每段至少选 1 个,相邻段会产生上升位。

接下来考虑一个基于构造的证明,可以在证明答案能取到 k-1 的同时,求解出最短的子序列长度。

上述过程可以理解成,我们当前所选的数字在序列上跳的过程;而 x 会跳到哪里只和初始序列和 x 本身有关!

所以我们对于每个 x 预处理出 x 跳一步之后会跳到哪里。这个可以倍增优化。具体地,对于每次询问 [l, r],如果 l, r 同属一段就输出 1;否则就从 l 所在段最小值开始跳,直到跳到 r 所属段为止即可。

复杂度 O(n \log n)

#include<bits/stdc++.h>
#define endl '\n'
#define N 200006
using namespace std;
int n,q,c,a[N],bel[N],pos[20][N];
pair<int,int> b[N];
main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1,j;i<=n;i=j+1)
    {
        for(j=i;j<n&&a[j]>a[j+1];j++);
        b[++c]={i,j};
        for(int k=i;k<=j;k++)bel[k]=c;
    }
    bel[n+1]=c+1;
    for(int j=0;j<20;j++)
        for(int i=1;i<=n+1;i++)pos[j][i]=n+1;
    for(int i=1;i<=n;i++)
    {
        if(bel[i]==c)continue;
        if(a[i]>a[b[bel[i]+1].first])pos[0][i]=b[bel[i]].second;
        else {
            int l=b[bel[i]+1].first;
            int r=b[bel[i]+1].second;
            while(l<=r)
            {
                int mid=l+r>>1;
                if(a[mid]>a[i])pos[0][i]=mid,l=mid+1;
                else r=mid-1;
            }
        }
    }
    for(int i=1;i<20;i++)
        for(int j=1;j<=n;j++)pos[i][j]=pos[i-1][pos[i-1][j]];
    while(q--)
    {
        int l,r; scanf("%d%d",&l,&r);
        if(bel[l]==bel[r]) {printf("1\n"); continue;}
        int ans=1,st=b[bel[l]].second;
        for(int i=19;~i;i--)
            if(bel[pos[i][st]]<bel[r])ans+=1<<i,st=pos[i][st];
        printf("%d\n",ans+1);
    }
    return 0;
}