CF1849E - Max to the Right of Min

题目传送门

单调栈,启发式分裂,ad-hoc。

题意

给定一个排列,求有多少个子数组满足最小值的下标小于最大值的下标。(\(n\le10^6\))

题解

这题乍一看没有什么思路,想了想DP还不好转移,总结了一下发现是因为确定不了枚举顺序。

所以我们改变枚举策略,每次计算 \(a_i\) 作为区间最大值对答案的贡献。

先看看暴力怎么做,首先设 \([l,r]\) 是 \(a_i\) 作为区间最大值的最大区间,那么我们可以枚举子数组的左端点,维护 \([l,i)\) 的最小值 \(mn\),计算其对答案的贡献,由于我们发现在左端点固定的情况下,符合条件的右端点是一段前缀区间,而边界就是右边第一个小于 \(mn\) 的位置。时间复杂度是 \(O(n^2)\) 的。

上面的东西都可以用单调栈维护。

考虑优化,我们发现上面的算法是枚举左端点,并对右端点统计答案,显然我们反过来,枚举右端点,并对左端点统计答案也是可以的。

这里注意到两边的枚举次数之和等于区间长度,于是我们考虑哪侧短就枚举哪侧,这样子单次枚举复杂度最多是区间长度的一半,我们将其叫做「启发式分裂」。

下面算一下时间复杂度,我们建出笛卡尔树,那么时间复杂度就是每个点较小的子树的大小之和,也就是 \(T(n)=2T(n)+O(\frac n 2)=O(n \log n)\)。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
const int INF=1e18;
int n,ans;
int st[N],top;
int a[N],lmx[N],rmx[N],lmn[N],rmn[N];

void calc1(int l,int x,int r){
    int mn=INF,y;
    for(int i=x-1;i>=l;i--){
        if(mn>a[i]){
            mn=a[i];
            y=rmn[i];
        }
        ans+=min(y,r)-x+1;
    }
}

void calc2(int l,int x,int r){  
    int mn=INF,y;
    ans+=x-l;//右端点为 x 时单独计算。
    for(int i=x+1;i<=r;i++){
        if(mn>a[i]){
            mn=a[i];
            y=lmn[i]-1;
        }
        ans+=max(y-l+1,0LL);
    }
}

signed main(){
    cin.tie(nullptr)->sync_with_stdio(false);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    st[top=0]=0,a[0]=INF;
    for(int i=1;i<=n;i++){
        while(a[st[top]]<a[i]) top--;
        lmx[i]=st[top]+1;
        st[++top]=i;
    }
    st[top=0]=n+1,a[n+1]=INF;
    for(int i=n;i>=1;i--){
        while(a[st[top]]<a[i]) top--;
        rmx[i]=st[top]-1;
        st[++top]=i;
    }
    st[top=0]=0,a[0]=-INF;
    for(int i=1;i<=n;i++){
        while(a[st[top]]>a[i]) top--;
        lmn[i]=st[top]+1;
        st[++top]=i;
    }
    st[top=0]=n+1,a[n+1]=-INF;
    for(int i=n;i>=1;i--){
        while(a[st[top]]>a[i]) top--;
        rmn[i]=st[top]-1;
        st[++top]=i;
    }
    for(int i=1;i<=n;i++){
        int l=lmx[i],r=rmx[i];
        if(i-l<r-i) calc1(l,i,r);
        else calc2(l,i,r);
    }
    cout<<ans;
    return 0;
}