树模板

线段树

线段树

  • 思想
    将整个区间划分成均等两半,对于每一半继续向下分为两段,直到分到单点。
    每一段区间都维护一个区间内的答案(或者别的什么和答案有关的东西,比如线性基),然后在查询区间答案时就可以用不超过 \(\log{n}\) 个区间凑出查询区间的答案。
    所有的修改可以通过懒标记下分到几个区间来记录,直到查询或另一次修改需要深入这段较大的区间时,再下传 (\(pushdown\))。
    每次修改操作完成后需要向上面更大的区间传答案 (\(pushup\))。
    由于每个点的坐标是直接 \(<<1\)\(<<1|1\) 算出来的,可能存在浪费区间的情况,一般树的数组需要多开几倍。

  • 题目:P3372 【模板】线段树 1

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=1e5+10;

#define ll long long

int n,m;
int a[N];

struct memr{
	int l,r;
	ll sum;
	int tg,siz;
}tr[N<<4];

void pushup(int p){
	tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
	return ;
}

void pushdown(int p){
	if(tr[p].tg){
		tr[p<<1].sum+=1ll*tr[p<<1].siz*tr[p].tg;
		tr[p<<1|1].sum+=1ll*tr[p<<1|1].siz*tr[p].tg;
		tr[p<<1].tg+=tr[p].tg;
		tr[p<<1|1].tg+=tr[p].tg;
		tr[p].tg=0;
	}
	return ;
}

void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	tr[p].siz=r-l+1;
	if(l==r){
		tr[p].sum=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
	return ;
}

void change(int p,int l,int r,int v){
	if(l<=tr[p].l && tr[p].r<=r){
		tr[p].sum+=1ll*v*tr[p].siz;
		tr[p].tg+=v;
		return ;
	}
	pushdown(p);
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid) change(p<<1,l,r,v);
	if(mid<r) change(p<<1|1,l,r,v);
	pushup(p);
	return ;
}

ll ask(int p,int l,int r){
	if(l<=tr[p].l && tr[p].r<=r){
		return tr[p].sum;
	}
	pushdown(p);
	int mid=(tr[p].l+tr[p].r)>>1;
	ll cnt=0;
	if(l<=mid) cnt+=ask(p<<1,l,r);
	if(mid<r) cnt+=ask(p<<1|1,l,r);
	pushup(p);
	return cnt;
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;++i)
		a[i]=read();
	build(1,1,n);
	int opt,l,r,d;
	while(m--){
		opt=read(),l=read(),r=read();
		if(opt==1){
			d=read();
			change(1,l,r,d);
		}
		else printf("%lld\n",ask(1,l,r));
	}
	return 0;
}

李超线段树

  • 好用,但没什么地方用(

  • 思想
    把普通线段树维护的 \(sum\)\(max\) 等值变为一条直线 \(kx+b\)\(k\)\(b\)
    一般的操作是在某区间加入一条直线、查询某单点的最大/最小值。
    写法有很多种,这里写的是个人认为比较好写也比较好记的一种方式。

    • 对于操作:与线段树一样,下分到 \(\log{n}\) 个区间。但和线段树不同的是,它并非下分到 \(\log{n}\) 个区间后就能直接对答案进行操作,而是需要对这个区间两个端点计算值,如果当前直线与记录的直线在区间内相交了,还需继续下分,直到某个区间内不能更改或全部被覆盖为新线段。
    • 对于查询,每次查单点所在所有线段树区间,计算答案即可。
  • 题目:P4254 [JSOI2008]Blue Mary开公司

code
#include<bits/stdc++.h>
using namespace std;

const int N=5e4+10;
const double eps=1e-8;

struct memr{
	int l,r;
//	double mx;
	double k,b;
}tr[N<<4];

int n;

double count(int x,double k,double b){
	return 1.0*x*k+b;
}

inline void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	tr[p].k=tr[p].b=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	return ;
}

void change(int p,int l,int r,double k,double b){
	if(l<=tr[p].l && tr[p].r<=r){
		if(!tr[p].k && !tr[p].b){
			tr[p].k=k,tr[p].b=b;
			return ;
		}
		double l1=count(tr[p].l,tr[p].k,tr[p].b);
		double r1=count(tr[p].r,tr[p].k,tr[p].b);
		double l2=count(tr[p].l,k,b);
		double r2=count(tr[p].r,k,b);
		if(l1<=l2 && r1<=r2){
			tr[p].k=k,tr[p].b=b;
			return ;
		}
		if(l2<=l1 && r2<=r1)
			return ;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid) change(p<<1,l,r,k,b);
	if(mid<r) change(p<<1|1,l,r,k,b);
	return ;
}

double ask(int p,int x,double ans){
	ans=max(ans,tr[p].k*x*1.0+tr[p].b);
	if(tr[p].l==x && tr[p].r==x)
		return ans;
	int mid=(tr[p].l+tr[p].r)>>1;
	if(x<=mid) return ask(p<<1,x,ans);
	return ask(p<<1|1,x,ans);
}

int main(){
//	freopen("company.in","r",stdin);
//	freopen("company.out","w",stdout);
	scanf("%d",&n);
	build(1,1,5e4);
	string opt;
	double x,y;
	int t;
	while(n--){
		cin>>opt;
		if(opt[0]=='Q'){
			scanf("%d",&t);
			printf("%d\n",(int)floor(1.0*ask(1,t,0)/100.0));
		}
		else{
			scanf("%lf%lf",&x,&y);
			change(1,1,5e4,y,x-y);
		}
	}
	return 0;
}

*动态开点

  • 有一种情况是,我们要对一个很大的范围建立线段树,如果按正常线段树开空间会炸;而其中并不是每一个点都需要用到的时候,我们使用动态开点来减少空间的浪费。

  • 相当于将原来可以直接计算的 \(p<<1\)\(p<<1|1\) 关系断开,手动设置左右儿子,直到需要这个点的时候才新建这个点(但每个点对应的区间还是从上面按原规律传下来的)。

主席树/可持久化线段树

  • 一种支持查询历史版本的树状结构(可持久化一般就指可以查询历史版本的数据结构)

  • 如果对于每一个历史版本都建一棵树,那么空间的浪费将会非常巨大。
    考虑到每一次操作(单点修改),只会走一条链上的点,所以我们只需要对这些被改过的点建立这个历史版本,即每次只新增一条链即可。

  • 题目:P3834 【模板】可持久化线段树 2

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=2e5+10;

struct memr{
	int ls,rs;
	int val;
}tr[N<<5];

int n,m,cnt=0;
int a[N],b[N],bl;
int rt[N];

int build(int l,int r){
	int p=++cnt;
	tr[p].val=0;
	if(l==r) return p;
	int mid=(l+r)>>1;
	tr[p].ls=build(l,mid);
	tr[p].rs=build(mid+1,r);
	return p;
}

int change(int p,int x,int l,int r){
//	cerr<<p<<" "<<x<<" "<<l<<" "<<r<<endl;
	int d=++cnt;
	tr[d].ls=tr[p].ls,tr[d].rs=tr[p].rs;
	tr[d].val=tr[p].val+1;
	if(l==r) return d;
	int mid=(l+r)>>1;
	if(x<=mid)	tr[d].ls=change(tr[p].ls,x,l,mid);
	else tr[d].rs=change(tr[p].rs,x,mid+1,r);
	return d;
}

int ask(int l,int r,int k,int c,int d){
	if(c>=d) return c;
	int x=tr[tr[r].ls].val-tr[tr[l].ls].val;
	int mid=(c+d)>>1;
	if(x<k) return ask(tr[l].rs,tr[r].rs,k-x,mid+1,d);
	return ask(tr[l].ls,tr[r].ls,k,c,mid);
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;++i)
		a[i]=read(),b[i]=a[i];
	sort(b+1,b+n+1);
	bl=unique(b+1,b+n+1)-b-1;
	rt[0]=build(1,bl);
	for(int i=1;i<=n;++i){
		int t=lower_bound(b+1,b+bl+1,a[i])-b;
		rt[i]=change(rt[i-1],t,1,bl);
	}
	int l,r,k;
	while(m--){
		l=read(),r=read(),k=read();
		printf("%d\n",b[ask(rt[l-1],rt[r],k,1,bl)]);
	}
	return 0;
}

树链剖分

  • 思想

    主要用于处理对树上路径修改查询。
    将每个节点的子树最大的儿子称为重儿子,每个非叶子节点有且只有一个重儿子。对于一条全由重儿子构成的链叫做重链。对每一个重链记录其链顶。
    将树上节点按 \(dfs\) 序重编号之后,容易发现每一个子树属于相邻的一段区间。于是在深搜时优先处理重儿子,这样可以使得每一条重链都在一个连续区间。
    重链剖分完成后,容易发现这棵树变成了若干条重链,每一条重链都由链顶与它父亲的一条轻边相互连接,因此可以把树上路径转化成对若干条重链的修改查询操作。
    然后对重编号后的序列建线段树即可。

  • 题目:P3384 【模板】重链剖分/树链剖分

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e6+10;

int n,m,r,mod;

struct memr{
	int ls,rs,v;
	int siz,tg;
}tr[N];

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

int f[N],b[N],dep[N],son[N];//son重儿子 
int size[N],top[N],ind[N],val[N];//size子树大小,ind转换后节点编号对应 
int head[N<<2],ver[N<<2],nxt[N<<2],tot;//存树 
int cnt=0,a[N];

void add(int x,int y){//加边 
	ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
	ver[++tot]=x,nxt[tot]=head[y],head[y]=tot;
}

void get_son(int p,int fa,int de){//求重儿子、重链、(父亲 
	f[p]=fa;
	dep[p]=de;
	for(int i=head[p];i;i=nxt[i]){
		if(ver[i]==fa) continue;
		get_son(ver[i],p,de+1);
		size[p]+=size[ver[i]];
		if(size[ver[i]]>=size[son[p]])
			son[p]=ver[i];
	}
	size[p]++;
	return ;
}

void cont(int p,int t){//连接重链、记录节点编号对应关系 
	ind[p]=++cnt;
	top[p]=t;
	a[cnt]=val[p];
//	if(!son[p]) return;
	if(son[p]) cont(son[p],t);
	for(int i=head[p];i;i=nxt[i]){
		if(ver[i]==son[p] || ver[i]==f[p])
			continue;
		cont(ver[i],ver[i]);
	}
	return ;
}
//以下为线段树 
void pushup(int p){
	tr[p].v=tr[p<<1].v+tr[p<<1|1].v;
	return ;
}

void pushdown(int p){
	if(tr[p].tg){
		(tr[p<<1].v+=tr[p].tg*tr[p<<1].siz)%=mod;
		(tr[p<<1|1].v+=tr[p].tg*tr[p<<1|1].siz)%=mod;
		(tr[p<<1].tg+=tr[p].tg)%=mod;
		(tr[p<<1|1].tg+=tr[p].tg)%=mod;
		tr[p].tg=0;
	}
	return ;
}

void change(int p,int l,int r,int t){
	if(l<=tr[p].ls && tr[p].rs<=r){
		(tr[p].v+=(tr[p].siz*t)%mod)%=mod;
		tr[p].tg+=t;
		return ;
	}
	pushdown(p);
	int mid=(tr[p].ls+tr[p].rs)>>1;
	if(l<=mid) change(p<<1,l,r,t);
	if(r>mid) change(p<<1|1,l,r,t);
	pushup(p);
	return ;
}

int ask(int p,int l,int r){
	if(l<=tr[p].ls && tr[p].rs<=r)
		return tr[p].v;
	pushdown(p);
	int mid=(tr[p].ls+tr[p].rs)>>1;
	int ans=0;
	if(l<=mid) ans+=ask(p<<1,l,r)%mod;
	if(r>mid) ans+=ask(p<<1|1,l,r)%mod;
	return ans%mod;
}
//以上线段树 
void change_line(int x,int y,int z){//原树上更改 
	int a=top[x],b=top[y];
	while(a!=b){
		if(dep[a]<dep[b])
			swap(a,b),swap(x,y);
		change(1,ind[a],ind[x],z);
		x=f[a],a=top[x];
	}
	if(ind[x]>ind[y]) swap(x,y);
	change(1,ind[x],ind[y],z);
	return ;
}

int ask_line(int x,int y){//原树上查询 
	int a=top[x],b=top[y],ans=0;
	while(a!=b){
		if(dep[a]<dep[b])
			swap(a,b),swap(x,y);
		(ans+=ask(1,ind[a],ind[x]))%=mod;
		x=f[a],a=top[x];
	}
	if(ind[x]>ind[y]) swap(x,y);
	(ans+=ask(1,ind[x],ind[y]))%mod;
	return ans%mod;
}

void build(int p,int l,int r){//建线段树 
	tr[p].ls=l,tr[p].rs=r;
	tr[p].siz=r-l+1;
	if(l==r){
		tr[p].v=a[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
	return ;
}

int main(){
//	freopen("P3384_1.in","r",stdin);
	n=read(),m=read(),r=read(),mod=read();
	for(int i=1;i<=n;++i)
		val[i]=read();
	int x,y;
	for(int i=1;i<n;++i){
		x=read(),y=read();
		add(x,y);
	}
	f[r]=1,dep[r]=0;
	get_son(r,0,1);
	cont(r,r);
	build(1,1,n);
	int opt,z;
	for(int i=1;i<=m;++i){
		opt=read(),x=read();
		if(opt==4)
			printf("%d\n",ask(1,ind[x],ind[x]+size[x]-1)%mod);
		else{
			y=read();
			if(opt==1){
				z=read();
				change_line(x,y,z);
			}
			else if(opt==2)
				printf("%d\n",ask_line(x,y));
			else
				change(1,ind[x],ind[x]+size[x]-1,y);
		}
	}
	return 0;
}

平衡树

  • 一种维护一个大小关系(权值、排名等)的树状结构。一般使用二叉树结构来实现。
    一般的规律:\(key_{ls_x}<key_x<key_{rs_x}\)

Treap

  • 最普通的一种平衡树。
    依靠左旋和右旋来维护平衡。
    判断左右旋的依据是随机数,理论复杂度是 \(\log{n}\) 级别的。但不排除一种可能是运气非常好,以至于旋转完了之后生成了一条链

  • 支持插入数字、删除数字、查排名、查数、查前驱、查后继等操作。

  • 题目:P3369 【模板】普通平衡树

code
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;

int n,Inf=0x7fffffff;
int root,k=0;

struct memr{
	int l,r,val;
	int sum,dat,size;
//sum:the number of points value val
//dat:a random number to make the tree seemingly equal
//size:the size of the point and its sons
}tr[N];

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

void update(int p){//
	tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+tr[p].sum;
}

int _New(int v){//build a new point
	tr[++k].val=v;
	tr[k].dat=rand();
	tr[k].sum=tr[k].size=1;
	return k;
}

int get_rank(int p,int v){//get the rank of v
	if(p==0) return 0;
	if(tr[p].val==v)
		return tr[tr[p].l].size+1;
	if(v<tr[p].val)
		return get_rank(tr[p].l,v);
	return get_rank(tr[p].r,v)+tr[tr[p].l].size+tr[p].sum;
}

int get_number(int p,int rank){//get the val by rank
	if(p==0) return Inf;
	if(tr[tr[p].l].size>=rank)
		return get_number(tr[p].l,rank);
	if(tr[tr[p].l].size+tr[p].sum>=rank)
		return tr[p].val;
	return get_number(tr[p].r,rank-tr[tr[p].l].size-tr[p].sum);
}

void zig(int &p){//turn right
	int q=tr[p].l;
	tr[p].l=tr[q].r,tr[q].r=p,p=q;
	update(tr[p].r),update(p);
}

void zag(int &p){//turn left
	int q=tr[p].r;
	tr[p].r=tr[q].l,tr[q].l=p,p=q;
	update(tr[p].l),update(p);
}

int _Last(int v){//the one in front of v
	int ans=1;
	int p=root;
	while(p){
		if(v==tr[p].val){
			if(tr[p].l){
				p=tr[p].l;
				while(tr[p].r)
					p=tr[p].r;
				ans=p;
			}
			break;
		}
		if(tr[p].val<v && tr[p].val>tr[ans].val) ans=p;
		p=(v<tr[p].val)?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}

int _Next(int v){//the one behand v
	int ans=2;
	int p=root;
	while(p){
		if(v==tr[p].val){
			if(tr[p].r){
				p=tr[p].r;
				while(tr[p].l)
					p=tr[p].l;
				ans=p;
			}
			break;
		}
		if(tr[p].val>v && tr[p].val<tr[ans].val) ans=p;
		p=(v<tr[p].val)?tr[p].l:tr[p].r;
	}
	return tr[ans].val;
}

void insert(int &p,int v){//insert a number
	if(p==0){
		p=_New(v);
		return ;
	}
	if(tr[p].val==v){
		tr[p].sum++;
		update(p);
	}
	else if(tr[p].val>v){
		insert(tr[p].l,v);
		if(tr[p].dat<tr[tr[p].l].dat)
			zig(p);
	}
	else if(tr[p].val<v){
		insert(tr[p].r,v);
		if(tr[p].dat<tr[tr[p].r].dat)
			zag(p);
	}
    update(p);
	return ;
}

void remove(int &p,int v){//delete v
	if(p==0)	return ;
	if(tr[p].val==v){
		if(tr[p].sum>1){
			tr[p].sum--,update(p);
			return ;
		}
		if(tr[p].l || tr[p].r){
			if(tr[p].r==0 || tr[tr[p].l].dat>tr[tr[p].r].dat)
				zig(p),remove(tr[p].r,v);
			else zag(p),remove(tr[p].l,v);
			update(p);
		}
		else p=0;
		return ;
	}
	(tr[p].val<v)?remove(tr[p].r,v):remove(tr[p].l,v);
	update(p);
	return ;
}

void build(){//build a new tree
	_New(-Inf),_New(Inf);
	root=1,tr[1].r=2;
	update(root);
}

int main(){
	srand(time(0));
	n=read();
	build();
	int opt,x;
	while(n--){
		opt=read(),x=read();
		if(opt==1)//insert a point
			insert(root,x);
		else if(opt==2)//delete a point
			remove(root,x);
		else if(opt==3)//get the index of x
			printf("%d\n",get_rank(root,x)-1);
		else if(opt==4)//get No.x point
			printf("%d\n",get_number(root,x+1));
		else if(opt==5)
			printf("%d\n",_Last(x));
		else
			printf("%d\n",_Next(x));
	}
	return 0;
}

fhq Treap

  • 没有旋转操作,但维护平衡的依据仍然是随机数。也就是说这仍然是一个考验运气的算法
    维护形状依靠的是分裂和合并操作。

  • 同样支持普通 \(\mathtt{Treap}\) 的操作,同时能够非常方便地进行区间删除。

  • 参考博客

  • 题目:P3369 【模板】普通平衡树

  • 闲话:使用体验:fhq,我永远的神

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=1e5+10;
const int Inf=1e9+10;

struct memr{
	int ls,rs;
	int val,dat;
	int siz,num;
}tr[N];

int n;
int cnt=0;
int rt,a,b,c;

int _New(int v){
	++cnt;
//	tr[cnt].ls=tr[cnt].rs=0;
	tr[cnt].dat=rand();
	tr[cnt].val=v;
	tr[cnt].siz=1;
	return cnt;
}

void update(int p){
	tr[p].siz=tr[tr[p].ls].siz+tr[tr[p].rs].siz+1;
	return ;
}

void split(int p,int v,int &x,int &y){
//	printf("split:%d %d\n",p,v);
	if(!p){
		x=0,y=0;
		return ;
	}
	if(tr[p].val<=v){
		x=p;
		split(tr[p].rs,v,tr[p].rs,y);
	}
	else{
		y=p;
		split(tr[p].ls,v,x,tr[p].ls);
	}
	update(p);
	return ;
}

int merge(int x,int y){
//	printf("merge:%d %d\n",x,y);
	if(!x || !y) return x+y;
	if(tr[x].dat>tr[y].dat){
		tr[x].rs=merge(tr[x].rs,y);
		update(x);
		return x;
	}
	tr[y].ls=merge(x,tr[y].ls);
	update(y);
	return y;
}

void insert(int v){
//	printf("insert:%d\n",v);
	split(rt,v,a,b);
	rt=merge(merge(a,_New(v)),b);
	return ;
}

void delet(int v){
//	printf("delete:%d\n",v);
	split(rt,v,a,c);
	split(a,v-1,a,b);
	b=merge(tr[b].ls,tr[b].rs);
	rt=merge(merge(a,b),c);
	return ;
}

int valu(int k){
//	printf("value:%d\n",k);
	int p=rt;
	while(p){
		if(tr[tr[p].ls].siz+1==k)
			break;
		else if(tr[tr[p].ls].siz<k){
			k-=tr[tr[p].ls].siz+1;
			p=tr[p].rs;
		}
		else p=tr[p].ls;
	}
	return tr[p].val;
}

int rnk(int v){
//	printf("rank:%d\n",v);
	split(rt,v-1,a,b);
	int ans=tr[a].siz+1;
	rt=merge(a,b);
	return ans;
}

int last(int v){
//	printf("last:%d\n",v);
	split(rt,v-1,a,b);
	int p=a;
	while(tr[p].rs) p=tr[p].rs;
	int ans=tr[p].val;
	rt=merge(a,b);
	return ans;
}

int nxt(int v){
//	printf("next:%d\n",v);
	split(rt,v,a,b);
	int p=b;
	while(tr[p].ls) p=tr[p].ls;
	int ans=tr[p].val;
	rt=merge(a,b);
	return ans;
}

int main(){
//	freopen("P3369_3.in","r",stdin);
	srand(time(0));
	n=read();
	int opt,t;
	while(n--){
		opt=read(),t=read();
		if(opt==1)
			insert(t);
		else if(opt==2)
			delet(t);
		else if(opt==3)
			printf("%d\n",rnk(t));
		else if(opt==4)
			printf("%d\n",valu(t));
		else if(opt==5)
			printf("%d\n",last(t));
		else printf("%d\n",nxt(t));
//		cerr<<opt<<endl;
	}
	return 0;
} 
code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

#define ll long long

const int N=5e5+10;

struct memr{
	int ls,rs;//左 &右儿子 
	int dat,siz;//堆序 &子树大小 
	int val,sum,lmx,rmx,mx;//值 &和 &左端最大子段 &右端最大子段 &区间最大子段 
	int ph,fl;//推平 &翻转标记 
	#define ls(i) tr[i].ls
	#define rs(i) tr[i].rs
}tr[N];

int n,m,rt=0,cnt=0;
int a,b,c;
int rec[N],tot=0;//回收 

int _New(int v){//新建节点 
	int p=tot>0?rec[tot--]:++cnt;
	memset(tr+p,0,sizeof(memr));
	tr[p].sum=tr[p].val=tr[p].mx=v;
	tr[p].lmx=tr[p].rmx=max(0,v);
	tr[p].siz=1;
	tr[p].dat=rand();
	tr[p].fl=tr[p].ph=0;
	return p;
}

void pushup(int p){
	if(!p) return ;
	tr[p].siz=tr[ls(p)].siz+tr[rs(p)].siz+1;
	tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum+tr[p].val;
	tr[p].lmx=max(0,max(tr[ls(p)].lmx,tr[ls(p)].sum+tr[p].val+tr[rs(p)].lmx));
	tr[p].rmx=max(0,max(tr[rs(p)].rmx,tr[rs(p)].sum+tr[p].val+tr[ls(p)].rmx));
	tr[p].mx=max(tr[ls(p)].rmx+tr[rs(p)].lmx,0)+tr[p].val;
	if(ls(p)) tr[p].mx=max(tr[p].mx,tr[ls(p)].mx);
	if(rs(p)) tr[p].mx=max(tr[p].mx,tr[rs(p)].mx);
//	printf("up:%d %d %d %d\n",p,ls(p),rs(p),tr[p].sum);
	return ;
}

void cover(int p,int v){//推平子树 
	if(!p) return ;
	tr[p].val=v;
	tr[p].sum=tr[p].siz*v;
	tr[p].lmx=tr[p].rmx=max(0,tr[p].sum);
	tr[p].mx=max(v,tr[p].sum);
	tr[p].ph=1;
	return ;
}

void reverse(int p){//子树翻转 
	if(!p) return ;
	swap(ls(p),rs(p));
	swap(tr[p].lmx,tr[p].rmx);
	tr[p].fl^=1;
	return ;
}

void pushdown(int p){//下传标记 
	if(!p) return ;
	if(tr[p].ph){
		cover(ls(p),tr[p].val);
		cover(rs(p),tr[p].val);
		tr[p].ph=0;
	}
	if(tr[p].fl){
		reverse(ls(p));
		reverse(rs(p));
		tr[p].fl=0;
	}
	return ;
}

void split(int p,int k,int &x,int &y){
	if(!p){
		x=y=0;
		return ;
	}
	pushdown(p);
	if(tr[ls(p)].siz<k){
		x=p;
		split(rs(p),k-tr[ls(p)].siz-1,rs(p),y);
	}
	else{
		y=p;
		split(ls(p),k,x,ls(p));
	}
	pushup(p);
	return ;
}

int merge(int x,int y){//合并两棵树 
	if(!x || !y) return x+y;
	if(tr[x].dat>tr[y].dat){
		pushdown(x);
		rs(x)=merge(rs(x),y);
		pushup(x);
		return x;
	}
	pushdown(y);
	ls(y)=merge(x,ls(y));
	pushup(y);
	return y;
}

int in[N];

#define mid ((l+r)>>1)

int add(int l,int r){//对一个区间建树 
	if(l!=r)
		return merge(add(l,mid),add(mid+1,r));
	return _New(in[l]);
}

void insert(int pos,int k){//在pos后插入k个数 
//	cerr<<k<<endl;
	for(int i=1;i<=k;++i)
		in[i]=read();
	split(rt,pos,a,b);
	rt=merge(merge(a,add(1,k)),b);
	return ;
}

void recover(int p){//回收废点 
	if(!p) return ;
	rec[++tot]=p;
	recover(ls(p));
	recover(rs(p));
	return ;
}

void delet(int l,int r){//区间删除 l~r 
	split(rt,r,a,c);
	split(a,l-1,a,b);
	recover(b);
	rt=merge(a,c);
	return ;
}

void push(int l,int r,int x){//推平区间 
	split(rt,r,a,c);
	split(a,l-1,a,b);
	cover(b,x);
	rt=merge(merge(a,b),c);
	return ;
}

void rv(int l,int r){//区间翻转 
	split(rt,r,a,c);
	split(a,l-1,a,b);
	reverse(b);
	rt=merge(merge(a,b),c);
	return ;
}

int asksum(int l,int r){//区间和 
	split(rt,r,a,c);
	split(a,l-1,a,b);
	int ans=tr[b].sum;
	rt=merge(merge(a,b),c);
	return ans;
}

int main(){
//	freopen("P2042_2.in","r",stdin);
	srand(time(0));
	n=read(),m=read();
	insert(0,n);
	string opt;
	int pos,len,x;
	while(m--){
		cin>>opt;
		if(opt=="INSERT"){
			pos=read(),len=read();
			insert(pos,len);
		}
		else if(opt=="DELETE"){
			pos=read(),len=read();
			delet(pos,pos+len-1);
		}
		else if(opt=="MAKE-SAME"){
			pos=read(),len=read(),x=read();
			push(pos,pos+len-1,x);
		}
		else if(opt=="REVERSE"){
			pos=read(),len=read();
			rv(pos,pos+len-1);
		}
		else if(opt=="GET-SUM"){
			pos=read(),len=read();
			printf("%d\n",asksum(pos,pos+len-1));
		}
		else printf("%d\n",tr[rt].mx);
//		cerr<<opt<<endl;
//		cerr<<cnt<<" "<<tot<<endl;
//		printf("sum:%d\n",tr[rt].sum);
	}
	return 0;
}

Splay

  • 把普通 \(\mathtt{Treap}\) 的左旋和右旋合并成了一个操作。不依靠随机数来维护平衡。

  • 同样支持普通 \(\mathtt{Treap}\) 的操作,还可以指定根节点。

  • 题目:P3391 【模板】文艺平衡树

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=1e5+10;
const int Inf=1<<30;

int n,m,root,k=0;
int a[N];

struct memr{
	int son[2];
	int val,siz,tg,fr;
}tr[N];

void pushup(int p){
	tr[p].siz=tr[tr[p].son[0]].siz+tr[tr[p].son[1]].siz+1;
	return ;
}

void pushdown(int p){
	if(p && tr[p].tg){
		tr[tr[p].son[0]].tg^=1;
		tr[tr[p].son[1]].tg^=1;
		swap(tr[p].son[0],tr[p].son[1]);
		tr[p].tg=0;
	}
	return ;
}

void rotate(int x){
	int y=tr[x].fr;
	int z=tr[y].fr;
	pushdown(x),pushdown(y);
	int f=(x==tr[y].son[1]);
	tr[y].son[f]=tr[x].son[f^1];
	tr[tr[y].son[f]].fr=y;
	tr[z].son[(tr[z].son[1]==y)]=x;
	tr[x].fr=z;
	tr[x].son[f^1]=y;
	tr[y].fr=x;
	pushup(y); 
	return ;
}

void Splay(int x,int goal){
	for(int i=tr[x].fr;i!=goal;rotate(x),i=tr[x].fr){
//		cout<<x<<" "<<i<<endl;
		if(tr[i].fr!=goal){
			int a=(x==tr[tr[x].fr].son[1]);
			int b=(i==tr[tr[i].fr].son[1]);
			(a==b)?rotate(i):rotate(x);
		}
	}
	if(!goal) root=x;
	return ;
}

int find(int x){
	int y=root;
	while(1){
		pushdown(y);
		if(x<=tr[tr[y].son[0]].siz){
			y=tr[y].son[0];
			continue;
		}
		x-=tr[tr[y].son[0]].siz+1;
		if(!x) return y;
		y=tr[y].son[1];
	}
}

void rev(int l,int r){
	int a=find(l-1),b=find(r+1);
//	cout<<a<<" "<<b<<endl;
	Splay(a,0);
	Splay(b,a);//顺序误我 
//	puts("Splay Over.");
	int p=tr[tr[root].son[1]].son[0];
	tr[p].tg^=1;
	return ;
}

int build(int l,int r,int f){
	if(l>r) return 0;
	int p=++k;
	tr[p].siz=1;
	tr[p].tg=0;
	tr[p].fr=f;
	int mid=(l+r)>>1;
	tr[p].val=a[mid];
	tr[p].son[0]=build(l,mid-1,p);
	tr[p].son[1]=build(mid+1,r,p);
	pushup(p);//pushup好 
	return p;
}

void print(int p){
	if(!p) return;
	pushdown(p);
	print(tr[p].son[0]);
	if(tr[p].val!=Inf && tr[p].val!=-Inf)
		printf("%d ",tr[p].val);
	print(tr[p].son[1]);
	return ;
}

int main(){
	n=read(),m=read();
	int l,r;
	a[1]=-Inf,a[n+2]=Inf;
	for(int i=1;i<=n;++i)
		a[i+1]=i;
	root=build(1,n+2,0);
	for(int i=1;i<=m;++i){
		l=read(),r=read();
		rev(l+1,r+1);
	}
	print(root);
	return 0;
}

替罪羊树

  • 一种思想非常暴力的平衡树。
    定义 \(\alpha\) 为节点 \(x\) 中较大的那个儿子的大小在 \(x\) 的大小中的占比,最平衡的情况是完全二叉树的 \(\alpha=0.5\),最不平衡的情况是链的 \(\alpha=1\)
    一般人为界定 \(\alpha=0.5\sim 0.75\) 左右。

  • 只要扫到某个节点的 \(\alpha\) 值超过界限,就把这个节点及其子树全部摧毁,重构成一棵完全二叉树树再接回来。
    因为是惰性删除(直接打个标记,但这个点实际上还存在),所以统计节点大小时的情况要多一点。

  • 支持普通 \(Treap\) 的操作。但查前驱和后继不需要单独写函数。

  • 参考博客

  • 题目:P3369 【模板】普通平衡树

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

#define alpha 0.75

const int N=1e5+10;

int n,cnt=0,rt;

struct memr{
	int ls,rs;
	int v,dt;
	int s,sz,sd;
}tr[N<<1];

void update(int p){
	tr[p].s=tr[tr[p].ls].s+tr[tr[p].rs].s+1;
	tr[p].sz=tr[tr[p].ls].sz+tr[tr[p].rs].sz+tr[p].dt;
	tr[p].sd=tr[tr[p].ls].sd+tr[tr[p].rs].sd+(tr[p].dt!=0);
	return ;
}

bool ifrub(int p){//判断是否重构 
	if(!tr[p].dt) return 0;
	if(1.0*alpha*tr[p].s<=(double)max(tr[tr[p].ls].s,tr[tr[p].rs].s)) return 1;
	if((double)tr[p].sd<=1.0*alpha*tr[p].s) return 1;
	return 0;
}

int dfn[N],tot;

void unfold(int p){//取出所有点 
//	cerr<<"unfold:"<<p<<endl;
	if(!p) return ;
	unfold(tr[p].ls);
	if(tr[p].dt) dfn[tot++]=p;
	unfold(tr[p].rs);
	return ;
}

int build(int l,int r){//重构 
//	cerr<<"build:"<<l<<" "<<r<<endl;
	if(l>=r) return 0;
	int mid=(l+r)>>1;
	int p=dfn[mid];
	tr[p].ls=build(l,mid);
	tr[p].rs=build(mid+1,r);
	update(p);
	return p;
}

void rebuild(int &p){
//	cerr<<"rebuild:"<<p<<endl;
	tot=0;
	unfold(p);
	p=build(0,tot);
//	cerr<<"after rebuild:"<<p<<endl;
	return ;
}

void insert(int &p,int v){//插入数 
//	cerr<<"insert:"<<p<<" "<<v<<endl;
	if(!p){
		p=++cnt;
		if(!rt) rt=1;
		tr[p].v=v;
		tr[p].ls=tr[p].rs=0;
		tr[p].dt=tr[p].s=tr[p].sd=tr[p].sz=1;
		return ;
	}
	if(tr[p].v==v)
		++tr[p].dt;
	else if(tr[p].v<v) insert(tr[p].rs,v);
	else insert(tr[p].ls,v);
	update(p);
	if(ifrub(p)) rebuild(p);
	return ;
}

void delet(int &p,int k){
	if(!p) return ;
	if(tr[p].v==k){
		if(tr[p].dt) 
			--tr[p].dt;
	}
	else if(tr[p].v<k)delet(tr[p].rs,k);
	else delet(tr[p].ls,k);
	update(p);
	if(ifrub(p)) rebuild(p);
	return ;
}

int rnk(int p,int v){
	if(!p) return 1;
	if(tr[p].v==v && tr[p].dt) return tr[tr[p].ls].sz+tr[p].dt+1;
	if(tr[p].v>v) return rnk(tr[p].ls,v);
	return tr[tr[p].ls].sz+tr[p].dt+rnk(tr[p].rs,v);
}

int valu(int p,int k){
	if(!p) return 0;
	if(tr[tr[p].ls].sz<k && k<=tr[tr[p].ls].sz+tr[p].dt)
		return tr[p].v;
	if(tr[tr[p].ls].sz+tr[p].dt<k)
		return valu(tr[p].rs,k-tr[tr[p].ls].sz-tr[p].dt);
	return valu(tr[p].ls,k);
}

int main(){
//	freopen("P3369_3.in","r",stdin);
	n=read();
	int opt,x;
	while(n--){
		opt=read(),x=read();
//		cerr<<"opt:"<<opt<<" "<<x<<endl;
//		cerr<<"rt:"<<rt<<endl; 
		if(opt==1) insert(rt,x);
		else if(opt==2) delet(rt,x);
		else if(opt==3) printf("%d\n",rnk(rt,x-1));
		else if(opt==4) printf("%d\n",valu(rt,x));
		else if(opt==5) printf("%d\n",valu(rt,rnk(rt,x-1)-1));
		else if(opt==6) printf("%d\n",valu(rt,rnk(rt,x)));
	}
	return 0;
}

思想

  • 对原树/森林建立一个辅助树/森林,对原树实链剖分,对于每一条实链用一个 \(\mathtt{Splay}\) 维护其在原树上的深度顺序(即中序遍历得原深度顺序),若干棵 \(\mathtt{Splay}\) 以虚边连接构成整个图。

  • 每一个节点都记录它的父亲节点,但每一个父亲节点只能记录一个儿子。所有这样的双向关系都是实边,其他儿子→父亲的单向关系是虚边。

  • 相当于,对于一棵原树投射到辅助树上,所有的边都先是虚边,然后在有需要的时候变成实边。

  • 其他询问信息的维护基本通过正常 \(pushup\)\(pushdown\) 来维护。

函数简介

  • \(\mathtt{Splay}\) 所需函数:\(Splay(x)+rotate(x)\)

  • 其他函数:\(pushup(x)+pushdown(x)\)\(reverse(x)\)(实现区间翻转)

  • \(Access(x)\):将 \(x\)原树根节点的这条链变成实链。
    也就是将 \(x\) 到根节点这一路上的点加入同一棵 \(\mathtt{Splay}\)
    \(x\) 出发,先把 \(x\) 的儿子断了,再跳到它的父亲。每次把 \(fa_x\) 换到 \(\mathtt{Splay}\) 的根节点上,再把 \(rs_{fa_x}\) 设为 \(x\)。最后更新节点。

Access
void Access(int x){
	for(int i=0;x;i=x,x=tr[x].fa){
		Splay(x);
		rs(x)=i;
		pushup(x);
	}
	return ;
}
  • \(make\_root(x)\):将 \(x\) 变成原树根节点。
    先打通 \(x\) 到现根节点的实链,然后将 \(x\) 换到 \(\mathtt{Splay}\) 根节点。
    由于根节点变了,所以这一路上的所有点的深度都会变。比如 \(x→root\) 深度 \(4\sim 1\),现在变为 \(root→x\) 深度 \(4\sim 1\)。投射到 \(\mathtt{Splay}\) 上就是区间翻转。
make_root
void make_root(int x){
	Access(x);
	Splay(x);
	reverse(x);
	return ;
}
  • \(split(x,y)\):取出 \(x→y\) 的这条链。
    先把 \(x\) 变为原树根节点,再打通 \(y\)\(x\) 的实链,然后在 \(\mathtt{Splay}\) 上把 \(y\) 转到根节点。
Split
void Split(int x,int y){
	make_root(x);
	Access(y);
	Splay(y);
	return ;
}
  • \(Find(x)\):找到 \(x\) 所在原树根节点。
    先打通 \(x\) 到根节点的实链,再把 \(x\) 转到 \(\mathtt{Splay}\) 根节点。由于原树根节点的深度是整个 \(\mathtt{Splay}\) 里最小的,一直找 \(ls_x\) 就行了。
    最后再把 \(Splay\) 转回去。
    可以用于判断两点是否连通。
Find
int Find(int x){
	Access(x);
	Splay(x);
	pushdown(x);
	while(ls(x)){
		x=ls(x);
		pushdown(x);
	}
	Splay(x);
	return x;
}
  • \(Link(x,y)\):增加 \(x\)\(y\) 之间的边。
    \(x\) 变成原树根节点,然后让 \(y\) 成为它的父亲。
    有时需要判断两点是否已连通/是否直接有边相连。
Link
void Link(int x,int y){
	make_root(x);
	tr[x].fa=y;
	return ;
}
  • \(Cut(x,y)\):删掉 \(x\)\(y\) 之间的边。
    取出 \(x→y\) 的链,若相连,\(y\) 必定是 \(x\) 的父亲,所以判断一下是否满足父子关系以及 \(x\) 节点没有 \(rs\) 再直接清空即可。
    有时需要判断两点是否直接有边相连,再删除。
Cut
void Cut(int x,int y){
	Split(x,y);
	if(ls(y)==x && !rs(x))
		ls(y)=tr[x].fa=0;
	return ;
}
code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=1e5+10;

int n,m;

struct memr{
	int son[2],fr;
	int sum,val;
	int tg;
}tr[N];

#define isr(i) (tr[tr[i].fr].son[0]!=i && tr[tr[i].fr].son[1]!=i)
#define Get(i) (i==tr[tr[i].fr].son[1])

void rev(int p){
	swap(tr[p].son[0],tr[p].son[1]);
	tr[p].tg^=1;
	return ;
}

void pushup(int p){
	tr[p].sum=tr[tr[p].son[0]].sum^tr[p].val^tr[tr[p].son[1]].sum;
	return ;
}

void pushdown(int p){
	if(tr[p].tg){
		rev(tr[p].son[0]);
		rev(tr[p].son[1]);
		tr[p].tg=0;
	}
	return ;
}

void update(int p){
	if(!isr(p)) update(tr[p].fr);
	pushdown(p);
	return ;
}

void rotate(int x){
//	printf("rotate %d\n",x);
	int y=tr[x].fr;
	int z=tr[y].fr,f=Get(x);
	if(!isr(y)) tr[z].son[tr[z].son[1]==y]=x;
	tr[x].fr=z;
	tr[y].son[f]=tr[x].son[f^1];
	tr[tr[x].son[f^1]].fr=y;
	tr[x].son[f^1]=y;
	tr[y].fr=x;
	pushup(y),pushup(x);
	return ;
}

void Splay(int x){
//	printf("Splay %d\n",x);
	update(x);
	for(int i;i=tr[x].fr,!isr(x);rotate(x))
		if(!isr(i))
			rotate((Get(x)==Get(i))?i:x);
	return ;
}

void Access(int p){
//	printf("Access %d\n",p);
	int x,t=p;
	for(x=0;p;x=p,p=tr[p].fr){
		Splay(p);
		tr[p].son[1]=x;
		pushup(p);
	}
	Splay(t);
	return ;
}

void make_root(int p){
	Access(p);
	rev(p);
	return ;
}

int find(int p){
	Access(p);
	while(tr[p].son[0]) 
		pushdown(p),p=tr[p].son[0];
	Splay(p);
	return p;
}

void Link(int x,int y){
	make_root(x);
	if(find(y)!=x) 
		tr[x].fr=y;
	return ;
}

void Cut(int x,int y){
	make_root(x);
	if(find(y)==x && tr[x].son[1]==y && !tr[y].son[0]){
		tr[y].fr=tr[x].son[1]=0;
		pushup(x);
	}
	return ;
}

void fix(int x,int v){
	Splay(x);
	tr[x].val=v;
	pushup(x);
	return ;
}

int main(){
	n=read(),m=read();
	for(int i=1;i<=n;++i)
		tr[i].val=read();
	int opt,x,y;
	while(m--){
		opt=read(),x=read(),y=read();
		if(!opt){
			make_root(x);
			Access(y);
			printf("%d\n",tr[y].sum);
		}
		else if(opt==1)
			Link(x,y);
		else if(opt==2)
			Cut(x,y);
		else	fix(x,y);
	}
	return 0;
}

K-D Tree

  • \(\mathtt{OI\_Wiki\ KD-Tree}\)

  • 一种可以用来维护多维空间中的点的数据结构。

  • 基于平衡树的思想,每一层下分时轮流按照 \(k\) 个维度来进行区间的划分。
    如果需要支持插入操作,则需要使用替罪羊树的算法来维护树的平衡。

  • 一种好用的函数:

    nth_element(a+1,a+x+1,a+n+1);
    

    具体效果为将第 \(x\) 小的数放到第 \(x\) 位,然后其后的数字都大于它,前面的数字都小于它,但不一定是顺序的。

  • 题目:P4148 简单题(可插入的 KD Tree)

code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

#define alpha 0.73

const int N=2e5+5;

int n;
int lastans=0,rt;
int cnt=0;

struct Points{
	int x[2],v;
}rp[N];

struct memr{
	int l[2],r[2];
	int sum,siz,son[2];
	Points val;
	#define ls(p) tr[p].son[0]
	#define rs(p) tr[p].son[1]
}tr[N];

int rec[N],tot=0;
int pt=0;
int now;

int _New(){
	return tot?rec[tot--]:++cnt;
}

void pushup(int p){
	for(int i=0;i<2;++i){
		tr[p].l[i]=tr[p].r[i]=tr[p].val.x[i];
		if(ls(p)){
			tr[p].l[i]=min(tr[p].l[i],tr[ls(p)].l[i]);
			tr[p].r[i]=max(tr[p].r[i],tr[ls(p)].r[i]);
		}
		if(rs(p)){
			tr[p].l[i]=min(tr[p].l[i],tr[rs(p)].l[i]);
			tr[p].r[i]=max(tr[p].r[i],tr[rs(p)].r[i]);
		}
	}
	tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum+tr[p].val.v;
	tr[p].siz=tr[ls(p)].siz+tr[rs(p)].siz+1;
	return ;
}

bool cmp(Points a,Points b){
	return a.x[now]<b.x[now];
}

void unfold(int p){
	if(!p) return ;
	unfold(ls(p));
	rp[++pt]=tr[p].val;
	rec[++tot]=p;
	unfold(rs(p));
	return ;
}

int rebuild(int l,int r,int i){
	if(l>r) return 0;
	int p=_New();
	int mid=(l+r)>>1;
	now=i;
	nth_element(rp+l,rp+mid,rp+r+1,cmp);
	tr[p].val=rp[mid];
	ls(p)=rebuild(l,mid-1,i^1);
	rs(p)=rebuild(mid+1,r,i^1);
	pushup(p);
	return p;
}

void check(int &p,int i){
	if(max(tr[rs(p)].siz,tr[ls(p)].siz)>alpha*tr[p].siz){
		pt=0;
		unfold(p);
		p=rebuild(1,tr[p].siz,i);
	}
	return ;
}

void insert(int &p,Points val,int i){
	if(!p){
		p=_New();
		tr[p].val=val;
		ls(p)=rs(p)=0;
		pushup(p);
		return ;
	}
	if(val.x[i]<=tr[p].val.x[i])
		insert(ls(p),val,i^1);
	else insert(rs(p),val,i^1);
	pushup(p);
	check(p,i);
	return ;
}

int ask(int p,int xl,int yl,int xr,int yr){
	if(!p) return 0;
	if(xl>tr[p].r[0] || xr<tr[p].l[0]) return 0;
	if(yl>tr[p].r[1] || yr<tr[p].l[1]) return 0;
	if(xl<=tr[p].l[0] && yl<=tr[p].l[1] && xr>=tr[p].r[0] && yr>=tr[p].r[1])
		return tr[p].sum;
	int ans=0;
	if(xl<=tr[p].val.x[0] && xr>=tr[p].val.x[0] && yl<=tr[p].val.x[1] && yr>=tr[p].val.x[1])
		ans+=tr[p].val.v;
	ans+=ask(ls(p),xl,yl,xr,yr);
	ans+=ask(rs(p),xl,yl,xr,yr);
	return ans;
} 

int main(){
	n=read();
	int opt,a,b,c,d;
	while(opt=read()){
		if(opt==3) break;
		a=read()^lastans,b=read()^lastans,c=read()^lastans;
		if(opt==1){
			Points dx;
			dx.x[0]=a,dx.x[1]=b,dx.v=c;
			insert(rt,dx,0);
		}
		else{
			d=read()^lastans;
			if(a>c || b>d) lastans=0;
			else lastans=ask(rt,a,b,c,d);
			printf("%d\n",lastans);
		}
	}
	return 0;
}
posted @ 2023-02-03 09:14  Star_LIcsAy  阅读(21)  评论(0)    收藏  举报