CF1696D Permutation Graph 题解

· · 题解

可能更好的阅读体验

题目传送门

题目大意

给定一个长度为 n 排列 a
定义 \operatorname{mn}(i,j)=\min\limits_{k=i}^{j}a_k,\operatorname{mx}(i,j)=\max\limits_{k=i}^{j}a_k
然后建立一个 n 个节点图,如果 \operatorname{mn}(i,j)=a_i ,\operatorname{mx}(i,j)=a_j 或者 \operatorname{mn}(i,j)=a_j ,\operatorname{mx}(i,j)=a_i,那么就在 i,j 之间连一条无向边。
1 号点到 n 号点的最短路。

n\le 10^5

题目解析

首先根据连边的条件我们发现在最短路径中肯定不会往回走。
并且根据定义,如果 u<v<w u,vu,w 之间有边,那么 v,w 之间也肯定有边,所以每次尽量往编号大的节点走。
问题就转化为了求与当前点有边相连的编号最大的点。

考虑利用单调栈预处理出每个节点有边第一个比它大/小的点,然后往右跳,并且用线段树 / ST 表求出这一段的最值来判断当前的左端点是否为这个区间的最值即可。
时间复杂度 O(n\log n)

int n,ln,a[maxn],rx[maxn],ry[maxn],st[maxn],top;
int minx[maxn<<2],maxx[maxn<<2],L,R;
void up(int rt){ minx[rt]=mmin(minx[rt<<1],minx[rt<<1|1]); maxx[rt]=mmax(maxx[rt<<1],maxx[rt<<1|1]); }
void build(int l,int r,int rt){
    if(l==r){ minx[rt]=maxx[rt]=a[l]; return; } int mid=(l+r)>>1;
    build(l,mid,rt<<1); build(mid+1,r,rt<<1|1); up(rt); return;
}
int findmin(int l,int r,int rt){
    if(L<=l&&r<=R) return minx[rt]; int mid=(l+r)>>1,res=INF;
    if(mid>=L) res=mmin(res,findmin(l,mid,rt<<1));
    if(mid<R) res=mmin(res,findmin(mid+1,r,rt<<1|1));
    return res;
}
int findmax(int l,int r,int rt){
    if(L<=l&&r<=R) return maxx[rt]; int mid=(l+r)>>1,res=-1;
    if(mid>=L) res=mmax(res,findmax(l,mid,rt<<1));
    if(mid<R) res=mmax(res,findmax(mid+1,r,rt<<1|1));
    return res;
}
void work(){
    n=read(); ln=ceil(log2(n)); int i; for(i=1;i<=n;i++) a[i]=read(),rx[i]=ry[i]=0; build(1,n,1);
    top=1; st[top]=1; for(i=2;i<=n;i++){ while(a[st[top]]<a[i]&&top) rx[st[top]]=i,top--; st[++top]=i; }
    top=1; st[top]=1; for(i=2;i<=n;i++){ while(a[st[top]]>a[i]&&top) ry[st[top]]=i,top--; st[++top]=i; }
    int now=1,ri=1,cnt=0;
    while(1){
        now=ri; if(ri==n) break; ri=now+1; cnt++;
        if(a[now]<a[ri]) while(rx[ri]){ L=now; R=rx[ri]; if(findmin(1,n,1)!=a[now]) break; ri=rx[ri]; }
        else while(ry[ri]){ L=now; R=ry[ri]; if(findmax(1,n,1)!=a[now]) break; ri=ry[ri]; }
    } print(cnt),pc('\n');
}
int main(){
    //freopen("1.in","r",stdin);
    //freopen(".out","w",stdout)
    int T=read(); while(T--) work(); return 0;
}