模拟赛时碰到了,感觉这个东西很优美啊,学习一下。
经典应用就是与区间最值算贡献相关的一类问题。
我们以 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]\) 内即可。可以不显式建出笛卡尔树,保证递归结构正确即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#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_ 提供题目!