笛卡尔树分治

模拟赛时碰到了,感觉这个东西很优美啊,学习一下。
经典应用就是与区间最值算贡献相关的一类问题。
我们以 Special Segments of Permutation 这道题为例讲解一下这种技巧。
首先,我们可以将所有区间分为以 \(p_1\) 为最大值的区间、以 \(p_2\) 为最大值的区间……我们可以在整个序列中划出极长的一段 \([l,r]\),这一段的最大值所处的位置为 \(mid\),则可以递归到 \([l,mid-1]\)\([mid+1,r]\) 两端解决子问题。我们发现,笛卡尔树的结构完美符合递归的要求。我们可以考虑当前分治区间 \([l,r]\),区间最大值位置是 \(mid\),则只需在 \([l,mid]\)\([mid,r]\) 各选取一个端点即可得到一个以 \(p_{mid}\) 为最大值的区间。这个过程有点像 cdq 分治的思想,总结一下:

  • 找到区间最大值所在位置 \(mid\)

  • 处理跨越 \(mid\) 的区间,此时处理的区间最大值一定是 \(p_{mid}\)

  • 递归处理子区间 \([l,mid-1]\)\([mid+1,r]\)

考虑时间复杂度问题。由于区间不是对半均分,所以我们采用启发式的思想,保证统计答案时只枚举短区间,时间复杂度 \(O(n\log n)\),否则时间复杂度可能退化至 \(O(n^2)\)。以本题为例,由于 \(p_i\) 各不相同,所以可以直接处理出每个值对应的位置,假设 \([l,mid]\) 这一段较短,那么在这个区间内枚举 \(i\),每次查询 \(p_{mid}-p_i\) 是否在区间 \([mid,r]\) 内即可。可以不显式建出笛卡尔树,保证递归结构正确即可。

#include<cstdio>
template<typename T>
inline void read(T &x){
    bool f=0;x=0;char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    if(f) x=~x+1;
}
template<typename T,typename...Args>
void read(T &x,Args &...args){read(x);read(args...);}
constexpr int N=2e5+10;
int n,p[N],b[N],ans,P;
struct node{
    int pos,val;
    friend node operator+(const node &x,const node &y){
        return x.val<y.val?y:x;
    }
}tree[N<<2];
int query(int l,int r){
    l+=P-1,r+=P+1;
    node res={};
    while(l^1^r){
        if(~l&1) res=res+tree[l^1];
        if(r&1) res=res+tree[r^1];
        l>>=1,r>>=1;
    }
    return res.pos;
}
void solve(int l,int r){
    if(l>=r) return;
    int mid=query(l,r);
    solve(l,mid-1),solve(mid+1,r);
    if(mid-l<r-mid){
        for(int i=l;i<=mid;i++){
            int pos=b[p[mid]-p[i]];
            ans+=(pos>=mid&&pos<=r);
        }
    }
    else{
        for(int i=mid;i<=r;i++){
            int pos=b[p[mid]-p[i]];
            ans+=(pos>=l&&pos<=mid);
        }
    }
}
int main(){
    read(n);
    P=1;while(P<=n+1) P<<=1;
    for(int i=1;i<=n;i++){
        read(p[i]);
        b[p[i]]=i;
        tree[P+i]={i,p[i]};
    }
    for(int i=P-1;i;i--) tree[i]=tree[i<<1]+tree[i<<1|1];
    solve(1,n);
    printf("%d\n",ans);
    return 0;
}

习题:

感谢 @Kenma 提供题目!

posted @ 2025-08-22 17:52  headless_piston  阅读(46)  评论(1)    收藏  举报