树套树类试题思路记录

[ZJOI2013]K大数查询

洛谷P3332

如果不区分这n个集合,即将这些集合合并起来,那就是动态整体第kth。将数据离散化后按点值建立一颗权值主席树维护即可。
现在区分出了n个集合,插入和查询操作都要在指定 \([l,r]\) 区间内操作,我们不妨令原有的权值主席树的每一个节点都维护一个线段树,来维护n个集合。

image

考虑到最坏情况,要建立 \(n\) 数量级颗线段树,如果全部建满的话空间复杂度 \(O(n^2logn)\),无法接受。可以动态开点地建树,只有用到某个点再开这个点,节省空间。这样最坏的情况每次询问都新建点,空间占用 \(log^2n\),总的空间复杂度 \(O(mlog^2n)\),可以接受。

code

#include<bits/stdc++.h>
typedef long long ll;
using namespace std; 

inline ll read(){
	ll w=1,q=0;char ch=' ';
	while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch<='9' && ch>='0') q=q*10+ch-'0',ch=getchar();
	return w*q;
}

const int MAXN = 5e4+5,MAXQ = 2e7+25;
ll n,m,root[MAXN<<1],X[MAXN],cntn;
ll op[MAXN],L[MAXN],R[MAXN],C[MAXN];
struct Dynamic_Segment_Tree{
	#define mid ((l+r)>>1)
	ll val[MAXQ],L[MAXQ],R[MAXQ],tag[MAXQ],cnt;
	inline void push_down(int l,int r,int p){
		if(tag[p]==0) return;
		if(!L[p]) L[p]=++cnt;
		if(!R[p]) R[p]=++cnt;
		val[L[p]]+=tag[p]*(mid-l+1),val[R[p]]+=tag[p]*(r-mid);
		tag[L[p]]+=tag[p],tag[R[p]]+=tag[p],tag[p]=0;return;
	}
	void add(int l,int r,ll &p,int a,int b){
		if(!p) p=++cnt;
		if(a<=l && r<=b){val[p]+=r-l+1,tag[p]++;return;}
		push_down(l,r,p);
		if(a<=mid) add(l,mid,L[p],a,b);
		if(b>mid) add(mid+1,r,R[p],a,b);
		val[p]=val[L[p]]+val[R[p]];return;
	}
	ll query(int l,int r,ll &p,int a,int b){
		if(!p) p=++cnt;
		if(a<=l && r<=b) return val[p];
		push_down(l,r,p);int temp=0;
		if(a<=mid) temp+=query(l,mid,L[p],a,b);
		if(b>mid) temp+=query(mid+1,r,R[p],a,b);
		return temp;
	}
}tret;

//外层权值树 
#define mid ((l+r)>>1)
void modify(int l,int r,int p,int a,int b,int c){
	tret.add(1,n,root[p],a,b);
	if(l==r) return;
	if(c<=mid) modify(l,mid,p*2,a,b,c);
	else modify(mid+1,r,p*2+1,a,b,c);
	return;
}
ll query(int l,int r,int p,int a,int b,int c){
	if(l==r) return l;
	ll sum=tret.query(1,n,root[p*2+1],a,b);
	if(sum<c) return query(l,mid,p*2,a,b,c-sum);
	else return query(mid+1,r,p*2+1,a,b,c);
}

signed main(){
	n=read(),m=read();
	//离散化 
	for(int i=1; i<=m; i++){
		op[i]=read(),L[i]=read(),R[i]=read(),C[i]=read();
		if(op[i]==1) X[++cntn]=C[i];
	}
	sort(X+1,X+cntn+1);int len=unique(X+1,X+cntn+1)-X-1;
	for(int i=1; i<=m; i++){
		if(op[i]==1) C[i]=lower_bound(X+1,X+len+1,C[i])-X;
	}
	//查询 
	for(int i=1; i<=m; i++){
		if(op[i]==1){
			modify(1,len,1,L[i],R[i],C[i]);
		}else{
			printf("%lld\n",X[query(1,len,1,L[i],R[i],C[i])]);
		} 
	}
}

Dynamic Rankings

洛谷P2617

如果没有C操作,即查询静态区间第kth,对每个点及其前缀建一颗权值树,那么任意一段区间可以表示为两颗树相减。可以用主席树维护。
加上C操作后,每一次修改x位置的值都需要牵动一同修改第x颗到第n颗权值树,复杂度难以接受。由于每一颗权值树维护的是一个前缀,我们想到树状数组的操作,让第x颗树不再维护区间 \([1,x]\),而是维护 \([x-lowbit(x)+1,x]\) 的区间。每次修改都是稳定地修改 \(logn\) 颗树,这样总体时间复杂度 \(O(nlog^2n)\),可以接受。

code

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int q=0;char ch=' ';
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch<='9' && ch>='0') q=q*10+ch-'0',ch=getchar();
	return q;
} 

const int MAXN = 5e5+25;
int n,m,cnt,len,arr[MAXN],X[MAXN<<1];
int cntl,cntr,rot[MAXN],sumtre[2][205];

struct Question{
	char op;
	int a,b,k;
}Q[MAXN];
struct Persistable_Segment_Tree{
	int l,r,val;
}tre[MAXN*40];
void change(int l,int r,int &p,int pos,int k){
	if(!p) p=++cnt;
	tre[p].val+=k;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(pos<=mid) change(l,mid,tre[p].l,pos,k);
	else change(mid+1,r,tre[p].r,pos,k);
}
inline void pre_change(int x,int k){
	int pos=lower_bound(X+1,X+len+1,arr[x])-X;
	for(int i=x; i<=n; i+=i&-i) change(1,len,rot[i],pos,k);
	return;
}
int query(int l,int r,int k){
	if(l==r) return l;
	int mid=(l+r)>>1,sum=0;
	for(int i=1; i<=cntr; i++) sum+=tre[tre[sumtre[1][i]].l].val;
	for(int i=1; i<=cntl; i++) sum-=tre[tre[sumtre[0][i]].l].val;
	if(k<=sum){
		for(int i=1; i<=cntr; i++) sumtre[1][i]=tre[sumtre[1][i]].l;
		for(int i=1; i<=cntl; i++) sumtre[0][i]=tre[sumtre[0][i]].l;
		return query(l,mid,k);
	}else{
		for(int i=1; i<=cntr; i++) sumtre[1][i]=tre[sumtre[1][i]].r;
		for(int i=1; i<=cntl; i++) sumtre[0][i]=tre[sumtre[0][i]].r;
		return query(mid+1,r,k-sum);
	}
}

inline int pre_query(int a,int b,int k){
	memset(sumtre,0,sizeof(sumtre));
	cntl=cntr=0;
	for(int i=a-1; i; i-=(i&-i)) sumtre[0][++cntl]=rot[i];
	for(int i=b; i; i-=(i&-i)) sumtre[1][++cntr]=rot[i];
	return query(1,len,k);
}

signed main(){
    ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1; i<=n; i++) cin>>arr[i],X[++len]=arr[i];
	for(int i=1; i<=m; i++){
		cin>>Q[i].op;
		if(Q[i].op=='Q') cin>>Q[i].a>>Q[i].b>>Q[i].k;
		else cin>>Q[i].a>>Q[i].b,X[++len]=Q[i].b;
	}
	sort(X+1,X+len+1);
	len=unique(X+1,X+len+1)-X-1;
	for(int i=1; i<=n; i++) pre_change(i,1);
	for(int i=1; i<=m; i++){
		if(Q[i].op=='C'){
			pre_change(Q[i].a,-1),arr[Q[i].a]=Q[i].b;
			pre_change(Q[i].a,1);
		}else{
			printf("%d\n",X[pre_query(Q[i].a,Q[i].b,Q[i].k)]);
		}
	}
	return 0;
}

[CQOI2011]动态逆序对

洛谷P3157

由于要单点修改,思考一个点对逆序对数量的贡献。即位置在该点之前且值大于该点,和位置在该点之后且值小于该点的点的数量。用权值树可以维护。
同上题类似,第x颗权值树维护的不是区间 \([1,x]\),而是 \([x-lowbit(x)+1,x]\)

code

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
inline ll read(){
	ll w=1,q=0;char ch=' ';
	while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
	if(ch=='-') w=-1,ch=getchar();
	while(ch<='9' && ch>='0') q=q*10+ch-'0',ch=getchar();
	return q;
} 

const ll MAXN = 1e5+5;
ll n,m,a[MAXN],root[MAXN],pos[MAXN],ans;

//[CQOI2011]动态逆序对
//i<j && ai>aj

struct Persistable_Segment_Tree{
	#define mid ((l+r)>>1)
	ll val[MAXN*400],L[MAXN*400],R[MAXN*400],cnt;
	void modify(ll l,ll r,ll &p,ll pos,ll k){
	    if(!p){p=++cnt;}if(l==r){val[p]+=k;return;}
		if(!L[p]) L[p]=++cnt;
		if(!R[p]) R[p]=++cnt;
		if(pos<=mid) modify(l,mid,L[p],pos,k);
		else modify(mid+1,r,R[p],pos,k);
		val[p]=val[L[p]]+val[R[p]];return;
	}
	ll query_left(ll l,ll r,ll p,ll pos){
		if(l==r) return (pos==l?val[p]:0);
		if(pos<=mid) return (val[R[p]]+query_left(l,mid,L[p],pos));
		else return query_left(mid+1,r,R[p],pos);
	}
	ll query_right(ll l,ll r,ll p,ll pos){
		if(l==r) return (pos==l?val[p]:0);
		if(pos>mid) return (val[L[p]]+query_right(mid+1,r,R[p],pos));
		else return query_right(l,mid,L[p],pos);
	}
}tret;


inline ll lowbit(ll x){return x&-x;}
inline void change(ll x,ll k,ll pos){while(x<=n){tret.modify(1,n,root[x],pos,k),x+=lowbit(x);}}
inline ll sum_left(ll x,ll pos){ll ans=0;while(x>=1){ans+=tret.query_left(1,n,root[x],pos+1),x-=lowbit(x);}return ans;}
inline ll sum_right(ll x,ll pos){ll ans=0;while(x>=1){ans+=tret.query_right(1,n,root[x],pos-1),x-=lowbit(x);}return ans;}

signed main(){
	n=read(),m=read();
	for(ll i=1; i<=n; i++){
		a[i]=read();pos[a[i]]=i;
		change(pos[a[i]],1,a[i]);
	}
	for(ll i=1; i<=n; i++){
		ans+=sum_left(pos[a[i]],a[i]);
	}
	for(ll i=1; i<=m; i++){
		ll x=read();
		printf("%lld\n",ans);
		ans-=sum_left(pos[x],x);
		ans-=sum_right(n,x)-sum_right(pos[x]-1,x);
		change(pos[x],-1,x);
	}
}

[国家集训队]排队

洛谷P1975

统计动态逆序对,与上题相似。
发现交换 \((a,b)\) 只会影响 \([a,b]\) 区间的逆序对个数,那就统计改变前和改变后 \([a,b]\) 区间的逆序对个数即可。

code

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read(){
	int q=0;char ch=' ';
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch<='9' && ch>='0') q=q*10+ch-'0',ch=getchar();
	return q;
} 

const int MAXN = 2e4+5;
int n,m,len,arr[MAXN],root[MAXN],X[MAXN],ans;

struct Dynamic_Segment_Tree{
	#define mid ((l+r)>>1)
	int cnt,L[MAXN*400],R[MAXN*400],val[MAXN*400];
	void modify(int l,int r,int &p,int pos,int k){
		if(!p) p=++cnt;
		if(l==r){val[p]+=k;return;}
		if(pos<=mid) modify(l,mid,L[p],pos,k);
		if(pos>mid) modify(mid+1,r,R[p],pos,k);
		val[p]=val[L[p]]+val[R[p]];return;
	}
	int left_query(int l,int r,int p,int pos){//[p1,p2]区间小于pos的数量 
		if(!p) return 0;
		if(l==r) return (l==pos?val[p]:0);
		if(pos<=mid) return left_query(l,mid,L[p],pos);
		if(pos>mid) return (val[L[p]]+left_query(mid+1,r,R[p],pos));
	}
	int right_query(int l,int r,int p,int pos){
		if(!p) return 0;
		if(l==r) return (l==pos?val[p]:0);
		if(pos<=mid) return (val[R[p]]+right_query(l,mid,L[p],pos));
		if(pos>mid) return right_query(mid+1,r,R[p],pos);
	}
}tret;


inline int lowbit(int x){return x&-x;}
inline void add(int x,int k,int pos){while(x<=n){tret.modify(1,len,root[x],pos,k),x+=lowbit(x);}return;}
inline int sum_left(int x,int pos){int ans=0;while(x>=1){ans+=tret.left_query(1,len,root[x],pos-1),x-=lowbit(x);}return ans;}
inline int sum_right(int x,int pos){int ans=0;while(x>=1){ans+=tret.right_query(1,len,root[x],pos+1),x-=lowbit(x);}return ans;}

signed main(){
	n=read();
	for(int i=1; i<=n; i++) X[i]=arr[i]=read();
	sort(X+1,X+n+1);len=unique(X+1,X+n+1)-X-1;
	for(int i=1; i<=n; i++) arr[i]=lower_bound(X+1,X+len+1,arr[i])-X;
	for(int i=1; i<=n; i++) add(i,1,arr[i]);
	for(int i=1; i<=n; i++) ans+=sum_right(i,arr[i]);
	m=read();
	printf("%d\n",ans);
	for(int i=1; i<=m; i++){
		int a=read(),b=read();
		if(a>b) swap(a,b);
		ans-=sum_right(b,arr[b])-sum_right(a-1,arr[b]),ans-=sum_left(b,arr[a])-sum_left(a,arr[a]);
		if(arr[a]>arr[b]) ans++;
		add(a,1,arr[b]),add(a,-1,arr[a]);add(b,1,arr[a]),add(b,-1,arr[b]),swap(arr[a],arr[b]);
		ans+=sum_right(b,arr[b])-sum_right(a-1,arr[b]),ans+=sum_left(b,arr[a])-sum_left(a,arr[a]);
		if(arr[a]>arr[b]) ans--;
		printf("%d\n",ans);
	}
}

image

posted @ 2022-10-12 11:21  smy2006  阅读(64)  评论(1)    收藏  举报