[浅谈] 二维数据结构——树套树

Ⅰ.二维树状数组

例一:P3372 【模板】线段树 1

2023.1.10 14:32
回忆一下树状数组的区间修改与查询操作,后面有用。

维护差分数组 de[i]
则:

i=1na[i]
=(de[1])+(de[1]+de[2])+(de[1]+de[2]+de[3])+(de[1]+de[2]++de[n])
=de[1]×n+de[2]×(n1)++de[n]×1
=i=1nde[i]×(n+1)i=1nde[i]×i

例二:P4514 上帝造题的七分钟

2023.1.10 17:58
二维树状数组。

同上面那题一维的一样来推式子。

先推怎么差分,这个画个图推,想办法让其差分前缀和等于原数。然后得到下图:

所以差分的方法为:
(a,b)+1
(c+1,d+1)+1
(a,d+1)1
(c+1,b)1

可以修改了,对吧。


然后怎么查询:


i=1xj=1ya[i][j]
=
de[1][1]+(de[1][1]+de[1][2])+(de[1][1]+de[1][2]+de[1][3])++
(de[1][1]+de[2][1])+{(de[1][1]+de[1][2])+(de[2][1]+de[2][2])}++
(de[1][1]+de[2][1]+de[3][1])++

(发现对于一个 de[i][j] 从它到右下角的数都涉及它)
=i=1xj=1yde[i][j]×(xi+1)×(yi+1)
(然后我推了半天,发现画图能很好地推出来)

=i=1xj=1yde[i][j]×x×yde[i][j]×(i1)×yde[i][j]×(j1)×x+de[i][j]×(i1)×(j1)

上面的值都能维护了啊。
那么二维树状数组就结束了。

Code
#include<bits/stdc++.h>
// #define int long long
using namespace std;
const int N=3010;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,m,de[N][N],dei[N][N],dej[N][N],deij[N][N];
void ude(int x,int y,int k){
	for(int i=x;i<=n;i+=i&(-i))
		for(int j=y;j<=m;j+=j&(-j))
			de[i][j]+=k;
	return;
}
void udei(int x,int y,int k){
	for(int i=x;i<=n;i+=i&(-i))
		for(int j=y;j<=m;j+=j&(-j))
			dei[i][j]+=k;
	return;
}
void udej(int x,int y,int k){
	for(int i=x;i<=n;i+=i&(-i))
		for(int j=y;j<=m;j+=j&(-j))
			dej[i][j]+=k;
	return;
}
void udeij(int x,int y,int k){
	for(int i=x;i<=n;i+=i&(-i))
		for(int j=y;j<=m;j+=j&(-j))
			deij[i][j]+=k;
	return;
}
int query(int it[][N],int x,int y){
	int res=0;
	for(int i=x;i>0;i-=i&(-i))
		for(int j=y;j>0;j-=j&(-j))
			res+=it[i][j];
	return res;
}
int getsum(int x,int y){
	return query(de,x,y)*x*y-query(dei,x,y)*y-query(dej,x,y)*x+query(deij,x,y);
}
char Startrunning,opt;
signed main(){cin>>Startrunning;
	n=1+read(),m=1+read();
	while(scanf("%s",&opt)!=EOF){
		if(opt=='L'){
			int a=1+read(),b=1+read(),c=1+read(),d=1+read(),k=read();
			ude(a,b,k);
			ude(c+1,d+1,k);
			ude(a,d+1,-k);
			ude(c+1,b,-k);
			
			udei(a,b,k*(a-1));
			udei(c+1,d+1,k*c);
			udei(a,d+1,-k*(a-1));
			udei(c+1,b,-k*c);
			
			udej(a,b,k*(b-1));
			udej(c+1,d+1,k*d);
			udej(a,d+1,-k*d);
			udej(c+1,b,-k*(b-1));
			
			udeij(a,b,k*(a-1)*(b-1));
			udeij(c+1,d+1,k*c*d);
			udeij(a,d+1,-k*(a-1)*d);
			udeij(c+1,b,-k*(b-1)*c);
		}
		else{
			int a=1+read(),b=1+read(),c=1+read(),d=1+read();
			printf("%d\n",getsum(c,d)-getsum(a-1,d)-getsum(c,b-1)+getsum(a-1,b-1));
		}
	}
	return 0;
}

Ⅱ.二维线段树

例一:P3437 [POI2006]TET-Tetris 3D

Pre:标记永久化

即在修改时一路更新路过区间的值,在覆盖是打上永久标记。然后查询时一路带上标记。

它能够大量减少代码量但使用时要思考清楚。

Solution

题意是给定一个二维平面,每个点有一个权值,有两种操作,查询矩阵最大值,将矩阵赋值为一个更大的数。

考虑一维怎么做:一颗线段树,使用标记永久化。

考虑二维,对每个上面那棵树上的每个点维护一颗线段树,维护第二维的值,但是对不同的变量要开不同的树维护。

Code
#include<bits/stdc++.h>
using namespace std;
const int N=4010;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int D,S,n;
struct SegmentTreeY{
	int maxn[N],tag[N];
	void update(int u,int l,int r,int L,int R,int x){
		maxn[u]=max(x,maxn[u]);//?
		if(L<=l && r<=R){tag[u]=max(tag[u],x);return;}//?//?
		int mid=(l+r)>>1;
		if(L<=mid)update(u<<1,l,mid,L,R,x);
		if(R>mid) update(u<<1|1,mid+1,r,L,R,x);
		return;
	}
	int query(int u,int l,int r,int L,int R){
		if(L<=l && r<=R){return maxn[u];}//?
		int res=tag[u],mid=(l+r)>>1;
		if(L<=mid)res=max(res,query(u<<1,l,mid,L,R));
		if(R>mid) res=max(res,query(u<<1|1,mid+1,r,L,R));
		return res;
	}
};
struct SegmentTreeX{
	SegmentTreeY maxn[N],tag[N];
	void update(int u,int l,int r,int L,int R,int yl,int yr,int x){
		maxn[u].update(1,1,S,yl,yr,x);
		if(L<=l && r<=R){tag[u].update(1,1,S,yl,yr,x);return;}//?
		int mid=(l+r)>>1;
		if(L<=mid)update(u<<1,l,mid,L,R,yl,yr,x);
		if(R>mid) update(u<<1|1,mid+1,r,L,R,yl,yr,x);
		return;
	}
	int query(int u,int l,int r,int L,int R,int yl,int yr){
		if(L<=l && r<=R){return maxn[u].query(1,1,S,yl,yr);}//?
		int res=tag[u].query(1,1,S,yl,yr),mid=(l+r)>>1;
		if(L<=mid)res=max(res,query(u<<1,l,mid,L,R,yl,yr));
		if(R>mid) res=max(res,query(u<<1|1,mid+1,r,L,R,yl,yr));
		return res;
	}
}E1;
int main(){
	D=read(),S=read(),n=read();
	for(int o=1;o<=n;o++){
		int d=read(),s=read(),w=read(),x=read()+1,y=read()+1;
		E1.update(1,1,D,x,x+d-1,y,y+s-1,E1.query(1,1,D,x,x+d-1,y,y+s-1)+w);
	}
	printf("%d\n",E1.query(1,1,D,1,D,1,S));
	return 0;
}

Ⅲ.树状数组套线段树

显然,二维树状数组的局限性大,数据大就开不了二维数组;二维线段树的局限性也大,常数大,效率低。
所以我们可以用树状数组套线段树完美地解决这个问题。

具体查询的时候,先把涉及的线段树根节点全部存入一个数组或 vector 中,然后查询时把每棵树的区间加加减减得到合并后的区间(你不用真的合并),然后走到儿子节点时数组中的点也全部向儿子节点走。注意到这里查询是单点的,所以不会有分支,是一条路走到底,算法可行。

例一:P2617 Dynamic Rankings

内层用权值线段树,方便求出第 k 大值,外层用树状数组,修改时修改 logn 棵线段树,查询时同样也只用把 logn 棵线段树合并。其实这也就是传说中的带修主席树

Ancient Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+514;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){
		if(c=='-')f=-1;
		c=getchar(); 
	} 
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	return x*f;
}
int cnt,n,m,tot,len,Real[N*2],root[N],rcd_u[N],rcd_v[N],len_u,len_v;
struct st_lsh{
	int num,x,flag;
}a[N*2];
struct st_ask{
	int opt,l,r,k;
}q[N];
struct st_tree{
	int lson,rson,sum;
}e[N*400];
bool cmp1(st_lsh t1,st_lsh t2){
	return t1.num<t2.num;
}
bool cmp2(st_lsh t1,st_lsh t2){
	if(t1.flag!=t2.flag)return t1.flag<t2.flag;
	return t1.x<t2.x;
}
int lowbit(int u){
	return u&(-u);
}
void lsh(){
	int last=-1;
	sort(a+1,a+len+1,cmp1);
	for(int i=1;i<=len;i++){
		if(a[i].num!=last)tot++;
		last=a[i].num;
		Real[tot]=a[i].num;
		a[i].num=tot;
	}
	sort(a+1,a+len+1,cmp2);
	for(int i=n+1;i<=len;i++)
		q[a[i].x].r=a[i].num;
	return;
}
void pushup(int u){
	e[u].sum=e[e[u].lson].sum+e[e[u].rson].sum;
	return;
} 
void update(int &u,int l,int r,int val,int add){
	if(!u)u=++cnt;
	if(l==r){
		e[u].sum+=add;
		return;
	}
	int mid=(l+r)>>1;
	if(val<=mid)update(e[u].lson,l,mid,val,add);
	else update(e[u].rson,mid+1,r,val,add);
	pushup(u);
	return;
}
void prepare_update(int now,int val,int add){
	for(int i=now;i<=n;i+=lowbit(i))update(root[i],1,tot,val,add);
	return;
}
int query(int l,int r,int k){
	if(l==r)return l;
	int mid=(l+r)>>1;
	int del=0;
	for(int i=1;i<=len_u;i++)del+=e[e[rcd_u[i]].lson].sum;
	for(int i=1;i<=len_v;i++)del-=e[e[rcd_v[i]].lson].sum;
	if(del>=k){
		for(int i=1;i<=len_u;i++)rcd_u[i]=e[rcd_u[i]].lson;
		for(int i=1;i<=len_v;i++)rcd_v[i]=e[rcd_v[i]].lson;
		return query(l,mid,k);
	}
	else{
		for(int i=1;i<=len_u;i++)rcd_u[i]=e[rcd_u[i]].rson;
		for(int i=1;i<=len_v;i++)rcd_v[i]=e[rcd_v[i]].rson;
		return query(mid+1,r,k-del);
	}
} 
int prepare_query(int v,int u,int k){
	len_u=len_v=0;
	for(int i=u;i;i-=lowbit(i))rcd_u[++len_u]=root[i];
	for(int i=v;i;i-=lowbit(i))rcd_v[++len_v]=root[i];
	return query(1,tot,k);
}
int main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++)
		a[++len].num=read(),a[len].x=i,a[len].flag=1;
	for(int i=1;i<=m;i++){
		char tmp;
		cin>>tmp;
		if(tmp=='Q'){
			q[i].opt=1;	
			q[i].l=read(),q[i].r=read(),q[i].k=read();
		}
		else {
			q[i].opt=2;
			q[i].l=read(),q[i].r=read();
			a[++len].flag=2,a[len].num=q[i].r,a[len].x=i;
		}
	}
	lsh();
	for(int i=1;i<=n;i++)prepare_update(i,a[i].num,1);
	for(int i=1;i<=m;i++){
		if(q[i].opt==1)printf("%d\n",Real[prepare_query(q[i].l-1,q[i].r,q[i].k)]);
		else{
			prepare_update(q[i].l,a[q[i].l].num,-1);
			prepare_update(q[i].l,q[i].r,1);
			a[q[i].l].num=q[i].r;
		}
	}
	return 0;
}

例二:P3810 【模板】三维偏序(陌上花开)

二维偏序怎么做?按第一维排序,第二维用树状数组求前缀和做。
三维偏序怎么做?按第一维排序,第二,三维用二维前缀和做。
怎么求二维前缀和?使用二维树状数组似乎空间开不下,二维线段树时间吃不消,于是可以考虑树状数组套线段树。(动态开点,空间 ×64

Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,k,ans[N];
struct node{
	int x,y,z;
	bool operator<(const node &A)const{
		if(x==A.x){
			if(y==A.y)return z<A.z;
			return y<A.y;
		}
		return x<A.x;
	}
	bool operator==(const node &A)const{return x==A.x && y==A.y && z==A.z;}
}a[N];

int cnt,val[N<<6],ls[N<<6],rs[N<<6];
struct SegmentTree{
	int rt;
	void pushup(int u){
		val[u]=val[ls[u]]+val[rs[u]];return;//已知cnt[0]=0;
	}
	void update(int &u,int l,int r,int x,int t){
		if(!u)u=++cnt;
		if(l==r){val[u]+=t;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);return;
	}
	int query(int u,int l,int r,int L,int R){
		if(!u)return 0;
		if(L<=l && r<=R)return val[u];
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=query(ls[u],l,mid,L,R);
		if(R>mid)res+=query(rs[u],mid+1,r,L,R);
		return res;
	}
};

struct TreeArray{
	SegmentTree E2[N];
	void update(int x,int y,int t){
		for(int i=x;i<=k;i+=i&(-i))
			E2[i].update(E2[i].rt,1,k,y,t);
		return;
	}
	int query(int x,int y){
		int res=0;
		for(int i=x;i>0;i-=i&(-i))
			res+=E2[i].query(E2[i].rt,1,k,1,y);
		return res;
	}
}E1;
int main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++)a[i].x=read(),a[i].y=read(),a[i].z=read();
	sort(a+1,a+n+1);
	int tmp=1;
	for(int i=1;i<=n;i++){
		if(a[i]==a[i+1] && i+1<=n){tmp++;continue;}
		E1.update(a[i].y,a[i].z,tmp);
		int id=E1.query(a[i].y,a[i].z)-1;
		ans[id]+=tmp;tmp=1;
	}
	for(int i=0;i<n;i++)printf("%d\n",ans[i]);
	return 0;
} 

Ⅳ.线段树套平衡树

其实当你完成上面的学习后,线段树套平衡树也就不难理解的,树套树都这样,又有什么区别?

例一:P3380 【模板】二逼平衡树(树套树)

考虑将区间开成一棵线段树,线段树上每个节点开一棵平衡树。操作实现:

  1. 修改。直接把每个设计区间都删去原数,添加新数即可。
  2. 查询 val排名。统计区间设计平衡树中小于 val 的数的数量。
  3. 查询排名为 k 的数。二分这个数,然后就是操作二。
  4. 查询前驱,所有前驱取最大值。
  5. 查询后驱,所有后驱取最小值。

ADD:这份代码的复杂度是 O(nlog3n),瓶颈在于操作二的二分,但是我们只看操作 23 ,显然可以用带修主席树做到 O(nlog2n) ,所以如果同时写两棵树套树,这题可以做到 O(nlog2n)

Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+110,M=N*200;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
bool alarm1;
int n,m,a[M];

int cnt,rnd[M],ls[M],rs[M],val[M],sze[M];
struct FHQTree{
	int rt,x,y,z;
	void pushup(int u){sze[u]=sze[ls[u]]+sze[rs[u]]+1;return;}//sze[0]=0;
	int newnode(int v){
		rnd[++cnt]=rand();
		val[cnt]=v;
		sze[cnt]=1;
		return cnt;
	}
	void split(int u,int v,int &A,int &B){
		if(!u)A=B=0;
		else{
			if(val[u]<=v){A=u;split(rs[A],v,rs[A],B);}
			else{B=u;split(ls[B],v,A,ls[B]);}
			pushup(u);
		}
		return;
	}
	int merge(int A,int B){
		if(!A || !B)return A+B;
		if(rnd[A]<rnd[B]){rs[A]=merge(rs[A],B);pushup(A);return A;}
		else{ls[B]=merge(A,ls[B]);pushup(B);return B;}
	}
	void insert(int v){
		split(rt,v,x,y);
		rt=merge(x,merge(newnode(v),y));
		return;
	}
	void erase(int v){
		split(rt,v,x,z);
		split(x,v-1,x,y);//写成split(rt,v-1,x,y);调了我两天 
		y=merge(ls[y],rs[y]);
		rt=merge(merge(x,y),z);
		return;
	}
	int Smaller(int v){
		split(rt,v-1,x,y);
		int tmp=sze[x];
		rt=merge(x,y);
		return tmp;
	}
	int KthVal(int u,int k){
		if(!u)return 0;
		if(k<=sze[ls[u]])return KthVal(ls[u],k);
		if(k<=sze[ls[u]]+1)return val[u];
		return KthVal(rs[u],k-1-sze[ls[u]]);
	}
	int pre(int v){
		split(rt,v-1,x,y);
		int tmp;
		if(sze[x]==0)tmp=-2147483647;
		else tmp=KthVal(x,sze[x]);
		rt=merge(x,y);return tmp;
	}
	int nxt(int v){
		split(rt,v,x,y);
		int tmp;
		if(sze[y]==0)tmp=2147483647;
		else tmp=KthVal(y,1);
		rt=merge(x,y);return tmp;
	}
//	void Bark(int u){
//		if(!u)return;
//		cout<<val[u]<<endl;
//		Bark(ls[u]);
//		Bark(rs[u]);
//		return;
//	}
};

struct SegmentTree{
	FHQTree E2[M];
	void build(int u,int l,int r){
		if(l==r){E2[u].insert(a[l]);return;}
		int mid=(l+r)>>1;
		build(u<<1,l,mid);
		build(u<<1|1,mid+1,r);
		for(int i=l;i<=r;i++)E2[u].insert(a[i]);
		return;
	}
	void update(int u,int l,int r,int x,int k){//删除x位置上的数,添加y 
		if(l==r){
			E2[u].erase(a[x]);E2[u].insert(k);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(u<<1,l,mid,x,k);
		if(x>mid)update(u<<1|1,mid+1,r,x,k);
		E2[u].erase(a[x]);E2[u].insert(k);
		return;
	}
	int Smaller(int u,int l,int r,int L,int R,int v){
		if(L<=l && r<=R)return E2[u].Smaller(v);
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=Smaller(u<<1,l,mid,L,R,v);
		if(R>mid)res+=Smaller(u<<1|1,mid+1,r,L,R,v);
		return res;
	}
	int pre(int u,int l,int r,int L,int R,int v){
		if(L<=l && r<=R)return E2[u].pre(v);
		int mid=(l+r)>>1,res=-2147483647;
		if(L<=mid)res=max(res,pre(u<<1,l,mid,L,R,v));
		if(R>mid)res=max(res,pre(u<<1|1,mid+1,r,L,R,v));
		return res;
	}
	int nxt(int u,int l,int r,int L,int R,int v){
		if(L<=l && r<=R)return E2[u].nxt(v);
		int mid=(l+r)>>1,res=2147483647;
		if(L<=mid)res=min(res,nxt(u<<1,l,mid,L,R,v));
		if(R>mid)res=min(res,nxt(u<<1|1,mid+1,r,L,R,v));
		return res;
	} 
//	void Bark(int u,int l,int r,int L,int R){
//		if(L<=l && r<=R){
//			cout<<"E1:"<<l<<" "<<r<<endl;
//			E2[u].Bark(E2[u].rt);
//			return;
//		}
//		int mid=(l+r)>>1;
//		if(L<=mid)Bark(u<<1,l,mid,L,R);
//		if(R>mid)Bark(u<<1|1,mid+1,r,L,R);
//		return;
//	}
}E1;

bool alarm2;
int main(){
	srand('?');fprintf(stderr,"%.3lf",abs(&alarm1-&alarm2)/1024.0/1024.0);
	n=read(),m=read();for(int i=1;i<=n;i++)a[i]=read();
	E1.build(1,1,n);
	for(int o=1;o<=m;o++){
		int opt=read();
		if(opt==1){
			int l=read(),r=read(),k=read();
			printf("%d\n",E1.Smaller(1,1,n,l,r,k)+1);
		}
		if(opt==2){
			int l=read(),r=read(),k=read();
			int ansl=0,ansr=1e8,ans;
			while(ansl<=ansr){
				int mid=(ansl+ansr)>>1;
				if(E1.Smaller(1,1,n,l,r,mid)<=k-1)
					ans=mid,ansl=mid+1;
				else ansr=mid-1;
			}
			printf("%d\n",ans);
		}
		if(opt==3){
			int x=read(),k=read();
			E1.update(1,1,n,x,k);a[x]=k;
		}
		if(opt==4){
			int l=read(),r=read(),k=read();
			printf("%d\n",E1.pre(1,1,n,l,r,k));
		}
		if(opt==5){
			int l=read(),r=read(),k=read();
			printf("%d\n",E1.nxt(1,1,n,l,r,k));
		}
	}
	return 0;
}

Ⅴ.练习

P3157 [CQOI2011]动态逆序对

用树状数组套权值线段树维护一个二维前缀和,先求出初始逆序对数,然后每次删除时求出被删除的值的贡献(它后面比他小的数的数量 + 它前面比它大的数的数量) 。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N*128;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[N],b[N],ans;
int cnt,ls[M],rs[M],val[M];
struct SegmentTree{
	int rt;
	void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
	void update(int &u,int l,int r,int x,int t){
		if(!u)u=++cnt;
		if(l==r){val[u]+=t;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);return;
	}
	int query(int u,int l,int r,int L,int R){
		if(!u)return 0;
		if(L<=l && r<=R)return val[u];
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=query(ls[u],l,mid,L,R);
		if(R>mid)res+=query(rs[u],mid+1,r,L,R);
		return res;
	}
//	void bark(int u,int l,int r){
//		if(!u)return;
//		if(l==r){
//			cout<<l<<" "<<r<<" "<<val[u]<<endl;
//			return;
//		}
//		int mid=(l+r)>>1;
//		bark(ls[u],l,mid);
//		bark(rs[u],mid+1,r);
//		return;
//	}
};
struct TreeArray{
	SegmentTree E2[N];
	void update(int x,int y,int t){
		for(int i=x;i<=n;i+=i&(-i))
			E2[i].update(E2[i].rt,1,n,y,t);
		return;
	}
	int query(int x,int l,int r){
		int res=0;
		for(int i=x;i>0;i-=i&(-i))
			res+=E2[i].query(E2[i].rt,1,n,l,r);
		return res;
	}
//	void bark(){
//		for(int i=1;i<=n;i++){
//			cout<<"Now::"<<i<<endl;
//			E2[i].bark(E2[i].rt,1,n);
//		}
//		return;
//	}
}E1;
signed main(){
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		a[i]=read();b[a[i]]=i;
		E1.update(i,a[i],1);
		ans+=E1.query(i-1,a[i]+1,n);
	}
	for(int i=1;i<=m;i++){
		printf("%lld\n",ans);
		int x=read();
		ans-=E1.query(b[x]-1,x+1,n);
		ans-=E1.query(n,1,x-1)-E1.query(b[x],1,x-1);
		E1.update(b[x],x,-1);
	}
	return 0;
}

CF1093E Intersection of Permutations

我们发现, a 序列并无修改,所以把它映射为 1,2,3...n 会比较好处理,因为问题变成了求 b 在区间 [l2,r2] 中范围在 [l1,r2] 的数的个数,显然是个裸的树状数组套权值线段树。

但是出题人也发现自己出得太裸了,就想恶心一下我们,卡我们空间,我们可以在 update 时把值为零的点垃圾回收,就可以愉快地 AC 了。

Code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+110,M=N*148;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
bool alarm1;
int n,a[N],b[N],m;

int cyx[M],top;
int cnt,val[M],ls[M],rs[M];
struct SegmentTree{
	int rt;
	void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
	void update(int &u,int l,int r,int x,int t){
		if(!u){
			if(top)u=cyx[top--];
			else u=++cnt;
		}
		if(l==r){val[u]+=t;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);
		if(!val[u])cyx[++top]=u,u=0;
		return;
	}
	int query(int u,int l,int r,int L,int R){
		if(!u)return 0;
		if(L<=l && r<=R)return val[u];
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=query(ls[u],l,mid,L,R);
		if(R>mid)res+=query(rs[u],mid+1,r,L,R);
		return res;
	}
};
struct TreeArray{
	SegmentTree E2[N];
	void update(int x,int y,int t){
		for(int i=x;i<=n;i+=i&(-i))
			E2[i].update(E2[i].rt,1,n,y,t);
		return;
	}
	int query(int x,int l,int r){
		int res=0;
		for(int i=x;i>0;i-=i&(-i))
			res+=E2[i].query(E2[i].rt,1,n,l,r);
		return res;
	}
}E1;
bool alarm2;
int main(){
	fprintf(stderr,"%.3lf",abs(&alarm1-&alarm2)/1024.0/1024.0);
	n=read(),m=read();
	for(int i=1;i<=n;i++)a[read()]=i;
	for(int i=1;i<=n;i++)b[i]=a[read()];
	for(int i=1;i<=n;i++)E1.update(i,b[i],1);
	for(int i=1;i<=m;i++){
		int opt=read();
		if(opt==1){
			int l1=read(),r1=read(),l2=read(),r2=read();
			printf("%d\n",E1.query(r2,l1,r1)-E1.query(l2-1,l1,r1));
		}
		if(opt==2){
			int x=read(),y=read();
			E1.update(x,b[x],-1);
			E1.update(y,b[y],-1);
			swap(b[x],b[y]);
			E1.update(x,b[x],1);
			E1.update(y,b[y],1);
		}
	}
	return 0;
}

P3332 [ZJOI2013]K大数查询

我们观察到,如果这题是个单点修改,就是个裸的的带修线段树,但是它是区间修改,我们怎么办呢?考虑到外层是个树状数组,树状数组其实也是支持区间修改的,于是我便将树状数组的区间修改区间查询操作套到了树套树上(其实这题直接线段树套值域线段树也能过),结果树状数组写错调了半天。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+110,M=N*128;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,m;
int opt[N],ql[N],qr[N],cc[N],lsh[N],LshCnt,Lshed;

int ls[M],rs[M],cnt,val[M];
struct SegmentTree{
	int rt=0;
	void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
	void update(int &u,int l,int r,int x,int t){
		if(!u)u=++cnt;
		if(l==r){val[u]+=t;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);return;
	}
};
struct TreeArray{
	SegmentTree SonTree[N];
	void update(int x,int y,int t){
		for(int i=x;i<=n;i+=i&(-i))
			SonTree[i].update(SonTree[i].rt,1,Lshed,y,t);
		return;
	}
}E,Ei;

vector<int>rcdu,rcdv,iu,iv;
int query(int l,int r,int k,int cs1,int cs2){
	if(l==r)return l;
	int SRson=0,mid=(l+r)>>1;
	for(int i=0;i<(int)rcdu.size();i++)SRson+=val[rs[rcdu[i]]]*(cs1+1);
	for(int i=0;i<(int)iu.size();i++)SRson-=val[rs[iu[i]]];
	for(int i=0;i<(int)rcdv.size();i++)SRson-=val[rs[rcdv[i]]]*(cs2+1);
	for(int i=0;i<(int)iv.size();i++)SRson+=val[rs[iv[i]]];                          
	if(SRson<k){
		for(int i=0;i<(int)rcdu.size();i++)rcdu[i]=ls[rcdu[i]];
		for(int i=0;i<(int)iu.size();i++)iu[i]=ls[iu[i]];
		for(int i=0;i<(int)rcdv.size();i++)rcdv[i]=ls[rcdv[i]];
		for(int i=0;i<(int)iv.size();i++)iv[i]=ls[iv[i]];
		return query(l,mid,k-SRson,cs1,cs2);
	}
	else{
		for(int i=0;i<(int)rcdu.size();i++)rcdu[i]=rs[rcdu[i]];
		for(int i=0;i<(int)iu.size();i++)iu[i]=rs[iu[i]];
		for(int i=0;i<(int)rcdv.size();i++)rcdv[i]=rs[rcdv[i]];
		for(int i=0;i<(int)iv.size();i++)iv[i]=rs[iv[i]];
		return query(mid+1,r,k,cs1,cs2);
	}
}
int PreQuery(int l,int r,int k){
	rcdu.clear();iu.clear();
	rcdv.clear();iv.clear();
	for(int i=r;i>0;i-=i&(-i))rcdu.push_back(E.SonTree[i].rt);
	for(int i=l-1;i>0;i-=i&(-i))rcdv.push_back(E.SonTree[i].rt);
	for(int i=r;i>0;i-=i&(-i))iu.push_back(Ei.SonTree[i].rt);
	for(int i=l-1;i>0;i-=i&(-i))iv.push_back(Ei.SonTree[i].rt);
	return query(1,Lshed,k,r,l-1);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		opt[i]=read(),ql[i]=read(),qr[i]=read(),cc[i]=read();
		if(opt[i]==1)lsh[++LshCnt]=cc[i];
	}
	sort(lsh+1,lsh+LshCnt+1);
	Lshed=unique(lsh+1,lsh+LshCnt+1)-lsh-1;
	for(int i=1;i<=m;i++)if(opt[i]==1)cc[i]=lower_bound(lsh+1,lsh+Lshed+1,cc[i])-lsh;
	for(int i=1;i<=m;i++){
		if(opt[i]==1){
			E.update(ql[i],cc[i],1);
			E.update(qr[i]+1,cc[i],-1);
			Ei.update(ql[i],cc[i],ql[i]);
			Ei.update(qr[i]+1,cc[i],-qr[i]-1);
		}
		if(opt[i]==2)
			printf("%lld\n",lsh[PreQuery(ql[i],qr[i],cc[i])]);
	}
	return 0;
}

P1975 [国家集训队]排队

我用的树状数组套值域线段树。结果没想到离散化竟然会有笨蛋写错还调了那么久。

Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N*200;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int n,m,a[N],ans;
int lsh[N],LshCnt;
int cnt,ls[M],rs[M],val[M];
struct SegmentTree{
	int rt;
	void pushup(int u){val[u]=val[ls[u]]+val[rs[u]];return;}
	void update(int &u,int l,int r,int x,int t){
		if(!u)u=++cnt;
		if(l==r){val[u]+=t;return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);return;
	}
	int query(int u,int l,int r,int L,int R){
		if(!u)return 0;
		if(L<=l && r<=R)return val[u];
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res+=query(ls[u],l,mid,L,R);
		if(R>mid)res+=query(rs[u],mid+1,r,L,R);
		return res;
	}
//	void bark(int u,int l,int r){
//		if(!u)return;
//		cout<<l<<" "<<r<<" "<<val[u]<<endl;
//		if(l==r){
//			return;
//		}
//		int mid=(l+r)>>1;
//		bark(ls[u],l,mid);
//		bark(rs[u],mid+1,r);
//		return;
//	}
};
struct TreeArray{
	SegmentTree E2[N];
	void update(int x,int y,int t){
		for(int i=x;i<=n;i+=i&(-i))
			E2[i].update(E2[i].rt,1,LshCnt,y,t);
		return;
	}
	int query(int x,int l,int r){
		int res=0;
		for(int i=x;i>0;i-=i&(-i))
			res+=E2[i].query(E2[i].rt,1,LshCnt,l,r);
		return res;
	}
//	void bark(){
//		for(int i=1;i<=n;i++){
//			cout<<"Now::"<<i<<endl;
//			E2[i].bark(E2[i].rt,1,LshCnt);
//		}
//		return;
//	}
}E1;
signed main(){
	n=read();
	for(int i=1;i<=n;i++)lsh[i]=a[i]=read();
	sort(lsh+1,lsh+n+1);
	LshCnt=unique(lsh+1,lsh+n+1)-lsh-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(lsh+1,lsh+LshCnt+1,a[i])-lsh;
	for(int i=1;i<=n;i++){
		E1.update(i,a[i],1);
		ans+=E1.query(i-1,a[i]+1,LshCnt);
	}
	m=read();
	printf("%lld\n",ans);
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		ans-=E1.query(x-1,a[x]+1,LshCnt);
		ans-=E1.query(n,1,a[x]-1)-E1.query(x,1,a[x]-1);
		E1.update(x,a[x],-1);
		ans-=E1.query(y-1,a[y]+1,LshCnt);
		ans-=E1.query(n,1,a[y]-1)-E1.query(y,1,a[y]-1);
		E1.update(y,a[y],-1);
		swap(a[x],a[y]);
		ans+=E1.query(x-1,a[x]+1,LshCnt);
		ans+=E1.query(n,1,a[x]-1)-E1.query(x,1,a[x]-1);
		E1.update(x,a[x],1);
		ans+=E1.query(y-1,a[y]+1,LshCnt);
		ans+=E1.query(n,1,a[y]-1)-E1.query(y,1,a[y]-1);
		E1.update(y,a[y],1);
		printf("%lld\n",ans);
	}
	return 0;
}

CF960F Pathwalks

畸形树套树,其实是运用了树套树的思想。确切来说是图套树。考虑编号和权值有两个维度,二维偏序求 LIS 如果用树状数组每个点都开显然空间不够,所以考虑用动态开点线段树,权值 i 表示上条边权值为 i 的最大路径长度,树内单点修改,维护最大值即可。

Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+110,M=N*128;
int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	return x*f;
}
int val[M],ls[M],rs[M],cnt;
struct Segment{
	int rt;
	void pushup(int u){val[u]=max(val[ls[u]],val[rs[u]]);return;}
	void update(int &u,int l,int r,int x,int t){
		if(!u)u=++cnt;
		if(l==r){val[u]=max(val[u],t);return;}
		int mid=(l+r)>>1;
		if(x<=mid)update(ls[u],l,mid,x,t);
		if(x>mid)update(rs[u],mid+1,r,x,t);
		pushup(u);
		return;
	}
	int query(int u,int l,int r,int L,int R){
		if(!u)return 0;
		if(L<=l && r<=R)return val[u];
		int mid=(l+r)>>1,res=0;
		if(L<=mid)res=max(res,query(ls[u],l,mid,L,R));
		if(R>mid)res=max(res,query(rs[u],mid+1,r,L,R));
		return res;
	}
}tree[N];
int n,m,Led,ans;
int U[N],V[N],W[N];
int main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		U[i]=read(),V[i]=read(),W[i]=read()+1;
		Led=max(Led,W[i]);
	}
	for(int i=1;i<=m;i++){
		int u=U[i],v=V[i];
		int tmp=tree[u].query(tree[u].rt,1,Led,1,W[i]-1);
		tree[v].update(tree[v].rt,1,Led,W[i],tmp+1);
	}
	for(int u=1;u<=n;u++)
		ans=max(ans,tree[u].query(tree[u].rt,1,Led,1,Led));
	printf("%d\n",ans);
	return 0;
}
 
posted @   FJOI  阅读(207)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示