树套树学习笔记

8 篇文章 0 订阅
6 篇文章 0 订阅

题目:传送门

这是第一类入门级的树套树,线段树/树状数组套平衡树。用线段树或树状数组维护区间,再用平衡树维护对区间中的动态修改。相对于普通的直白操作会好一些,但会带好几个log

有人可能会疑惑为什么为每个区间开一棵平衡树不会MLE呢?我们以线段树为例,每一层的线段树的节点数都为n,至多有logn层。为每一个区间开一棵和区间大小相同的平衡树,总的平衡树大小只会在nlogn左右。

关于五个操作
其实关键就是一句话扎实的代码实现能力是复杂算法的基础

1、查询k在[l,r]的排名;对treap原来的求排名函数稍作修改为求比k小的数的个数。这样线段树分段查询再合并加1,就是k在l-r的排名。
时间复杂度:(log2n)[线段树,平衡树]

2、查询在[l,r]的区间中排名为k的元素。这个操作真没有什么高深的做法,二分值大小吧,再用操作一暴力。听说隔壁“线段树/树状数组套主席树”可以只带两个log,平衡树表示无奈。
时间复杂度:(log3n)[二分,线段树,平衡树]

3、将位置为pos的元素值修改为k。这个也很简单(暴力),线段树枚举pos所在的log个区间,每次从平衡树中删去原值,插入新值。

4、查找k在[l,r]中的前驱(要求严格小于)。线段树划分区间,再用平衡树暴力求各区间的前驱,最后合并前驱。

5、查询k在[l,r]中的后继(要求严格大于)。对操作四依葫芦画瓢。

【code】
这里将treap封装成了结构体,方便线段树的调用。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls t[x].ch[0]
#define rs t[x].ch[1]
using namespace std;
const int inf=2147483647;
const int maxn=5e4+1000;
int n,m;
int p[maxn];
struct node{
	int val,key,cnt,size;
	int ch[2];
};
node t[maxn*40];
int tot;
struct Treap{
	int root;
	void update(int x){
		t[x].size=t[ls].size+t[rs].size+t[x].cnt;
	}
	void rotate(int &x,int d){
		int son=t[x].ch[d];
		t[x].ch[d]=t[son].ch[d^1];
		t[son].ch[d^1]=x;update(x);update(x=son);
	}
	void insert(int &x,int val){
		if(!x){
			x=++tot;
			t[x].cnt=t[x].size=1;
			t[x].key=rand(),t[x].val=val;
			return ;
		}
		t[x].size++;
		if(t[x].val==val){t[x].cnt++;return;}
		int d=val>t[x].val;insert(t[x].ch[d],val);
		if(t[x].key>t[t[x].ch[d]].key) rotate(x,d);
	}
	void delet(int &x,int val){
		if(!x) return ;
		if(t[x].val==val){
			if(t[x].cnt>1){t[x].cnt--,t[x].size--;return ;}
			bool d=t[ls].key>t[rs].key;
			if(ls==0||rs==0) x=ls+rs;
			else rotate(x,d),delet(x,val);
		}
		else t[x].size--,delet(t[x].ch[t[x].val<val],val);
	}
	int rank(int x,int val){
		if(!x)return 0;
		if(t[x].val==val) return t[ls].size;
		if(t[x].val>val) return rank(ls,val);
		else return t[ls].size+t[x].cnt+rank(rs,val);
	}
	int kth(int x,int k){
		while(1){
			if(k<=t[ls].size)x=ls;
			else if(k>t[ls].size+t[x].cnt) k-=t[ls].size+t[x].cnt,x=rs;
			else return t[x].val;
		}
	}
	int pre(int x,int val){
		if(!x) return -inf;
		if(t[x].val>=val) return pre(ls,val);
		else return max(t[x].val,pre(rs,val));
	}
	int nxt(int x,int val){
		if(!x) return inf;
		if(t[x].val<=val) return nxt(rs,val);
		else return min(t[x].val,nxt(ls,val));
	}
}a[maxn<<2];
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9') tmp=getchar();
	while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
inline void read(int &x,int &y){read(x),read(y);}
inline void read(int &x,int &y,int &z){read(x),read(y),read(z);}

void build(int x,int l,int r){
	for(int i=l;i<=r;i++)
		a[x].insert(a[x].root,p[i]);
	if(l==r) return ;
	build(x<<1,l,l+r>>1);
	build(x<<1|1,(l+r>>1)+1,r);
}
int queryrank(int x,int l,int r,int ql,int qr,int num){
	if(l>qr||r<ql) return 0;
	if(ql<=l&&r<=qr)
		return a[x].rank(a[x].root,num);
	int ret=0;
	ret+=queryrank(x<<1,l,l+r>>1,ql,qr,num);
	ret+=queryrank(x<<1|1,(l+r>>1)+1,r,ql,qr,num);
	return ret;
}
int queryval(int ql,int qr,int k){
	int l=0,r=1e8,mid,ans=-1;
	while(l<=r){
		mid=l+r>>1;
		if(queryrank(1,1,n,ql,qr,mid)+1<=k)ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}
void modify(int x,int l,int r,int pos,int k){
	if(pos<l||r<pos) return ;
	a[x].delet(a[x].root,p[pos]);
	a[x].insert(a[x].root,k);
	if(l==r) return ;
	modify(x<<1,l,l+r>>1,pos,k);
	modify(x<<1|1,(l+r>>1)+1,r,pos,k);
}
int querypre(int x,int l,int r,int ql,int qr,int k){
	if(l>qr||r<ql) return -inf;
	if(ql<=l&&r<=qr) return a[x].pre(a[x].root,k);
	int ret=querypre(x<<1,l,l+r>>1,ql,qr,k);
	ret=max(ret,querypre(x<<1|1,(l+r>>1)+1,r,ql,qr,k));
	return ret;
}
int querynxt(int x,int l,int r,int ql,int qr,int k){
	if(l>qr||r<ql) return inf;
	if(ql<=l&&r<=qr) return a[x].nxt(a[x].root,k);
	int ret=querynxt(x<<1,l,l+r>>1,ql,qr,k);
	ret=min(ret,querynxt(x<<1|1,(l+r>>1)+1,r,ql,qr,k));
	return ret;
}
int main(){
	srand(19260817);
	cin>>n>>m;
	for(int i=1;i<=n;i++)read(p[i]);
	build(1,1,n);
	int op;
	for(int i=1;i<=m;i++){
		int l,r,k,pos;
		read(op);
		if(op==1){
			read(l,r,k);
			printf("%d\n",queryrank(1,1,n,l,r,k)+1);
		}
		else if(op==2){
			read(l,r,k);
			printf("%d\n",queryval(l,r,k));
		}
		else if(op==3){
			read(pos,k);
			modify(1,1,n,pos,k);p[pos]=k;
		}
		else if(op==4){
			read(l,r,k);
			printf("%d\n",querypre(1,1,n,l,r,k));
		}
		else{
			read(l,r,k);
			printf("%d\n",querynxt(1,1,n,l,r,k));
		}
	}
	return 0;
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值