【学习笔记】线段树二分

本文介绍了线段树结合二分查找解决区间查询问题的高效算法,通过固定一端点,二分另一端点的方式,实现了复杂度为O(nlogn)的时间复杂度。具体讨论了在CF689D题目中,求解满足特定条件的区间数量的方法,定义了qry1和qry2两个辅助函数,分别用于寻找满足不等式的最大和最小边界。利用单调性,确保了线段树在处理每个子区间时仅遍历一次,证明了算法的正确性和效率。
摘要由CSDN通过智能技术生成

这是真正的线段树二分
首先拆分询问区间。可以证明每一层遍历到的区间不会超过 4 4 4 个。所以复杂度是 log ⁡ n \log n logn
类似的,可以通过 递归左子树->等待答案->递归右子树 的方法来查询 [l,r] 中最右或最左的满足条件的点。复杂度同样是 log ⁡ n \log n logn

例题1:CF689D Friends and Subsequences

求满足下列条件的区间 [ l , r ] [l,r] [l,r] 的数量, n ≤ 2 e 5 n\leq 2e5 n2e5
在这里插入图片描述
solution:
固定 l l l 端点,二分 r r r 端点,设函数 q r y 1 ( p , L , R ) qry1(p,L,R) qry1(p,L,R) 表示 [ L , R ] [L,R] [L,R] 中满足 max ⁡ ( a l , a l + 1 , . . , a k ) > min ⁡ ( b l , b l + 1 , . . . , b k ) \max(a_l,a_{l+1},..,a_k)>\min(b_l,b_{l+1},...,b_k) max(al,al+1,..,ak)>min(bl,bl+1,...,bk) 的最小的点,其中 L ≤ k ≤ R L\le k\le R LkR

同理设 q r y 2 ( p , L , R ) qry2(p,L,R) qry2(p,L,R) 表示 [ L , R ] [L,R] [L,R] 中满足 max ⁡ ( a l , a l + 1 , . . . , a k ) < min ⁡ ( b l , b l + 1 , . . , b k ) \max(a_l,a_{l+1},...,a_k)<\min(b_l,b_{l+1},..,b_k) max(al,al+1,...,ak)<min(bl,bl+1,..,bk) 的最大的点,其中 L ≤ k ≤ R L\le k\le R LkR

那么用全局变量 M I N MIN MIN, M A X MAX MAX 记录遍历到的所有点中的最大值,最小值,每次递归左子树,等待答案,再递归右子树,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

关于 q r y 1 qry1 qry1
请添加图片描述

关于 q r y 2 qry2 qry2

请添加图片描述

上述两图证明了任意时刻都只会遍历左右子树中的一个(分叉点除外),复杂度得到证明。

总结:线段树二分做法依赖于单调性,做题时要善于观察。

#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int Maxn=2e5+5;
int n,a[Maxn],b[Maxn],MAX,MIN;
ll res;
struct SegmentTree{
	int Max[Maxn<<2],Min[Maxn<<2];
	void build(int p,int l,int r) {
		if(l==r) {
			Max[p]=a[l];
			Min[p]=b[l];
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		Max[p]=max(Max[p<<1],Max[p<<1|1]);
		Min[p]=min(Min[p<<1],Min[p<<1|1]);
	}
	int qry1(int p,int l,int r,int ql,int qr) {
		int mid=l+r>>1;
		if(ql<=l&&r<=qr) {
			int tmax=max(MAX,Max[p]);
			int tmin=min(MIN,Min[p]);
			if(tmax<=tmin) {
				MAX=tmax;
				MIN=tmin;
				return r+1;
			}
			if(l==r) return l;
			int now=qry1(p<<1,l,mid,ql,qr);
			if(now==mid+1) now=qry1(p<<1|1,mid+1,r,ql,qr);
			return now;
		}
		else if(ql<=mid&&mid<qr) {
			int now=qry1(p<<1,l,mid,ql,qr);
			if(now==mid+1) now=qry1(p<<1|1,mid+1,r,ql,qr);
			return now;
		} 
		else if(qr<=mid) return qry1(p<<1,l,mid,ql,qr);
		else return qry1(p<<1|1,mid+1,r,ql,qr);
	}
	int qry2(int p,int l,int r,int ql,int qr) {
		int mid=l+r>>1;
		if(ql<=l&&r<=qr) {
			int tmax=max(MAX,Max[p]);
			int tmin=min(MIN,Min[p]);
			if(tmax<tmin) {
				MAX=tmax;
				MIN=tmin;
				return r;
			}
			if(l==r) return l-1;
			int now=qry2(p<<1,l,mid,ql,qr);
			if(now==mid) now=qry2(p<<1|1,mid+1,r,ql,qr);
			return now;
		}
		else if(ql<=mid&&mid<qr) {
			int now=qry2(p<<1,l,mid,ql,qr);
			if(now==mid) now=qry2(p<<1|1,mid+1,r,ql,qr);
			return now;
		}
		else if(qr<=mid) return qry2(p<<1,l,mid,ql,qr);
		else return qry2(p<<1|1,mid+1,r,ql,qr);
	}
}T1;
int main() {
//	freopen("data.in","r",stdin);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++) {
		scanf("%d",&b[i]); 
	}
	T1.build(1,1,n);
	for(int i=1;i<=n;i++) {
		MAX=-INF,MIN=INF;
		int pr=T1.qry1(1,1,n,i,n);
		MAX=-INF,MIN=INF;
		int pl=T1.qry2(1,1,n,i,n);
		res+=pr-pl-1;
	}
	printf("%lld",res);
}
  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值