可持久化线段树总结

普通的可持久化线段树的用法就是利用动态开点,在一次修改中只新建有修改的结点,从而实现可以调用某次修改前的线段树。例如在单点修改中,每次更改的最多只有线段树的一条链,则修改需要的时间复杂度与空间复杂度均为 O ( log ⁡ N ) O(\log N) O(logN)

可持久化线段树也有一些拓展用法,例如较常见的可持久化权值线段树。求逆序对个数常见的一种做法即建立权值线段树(or树状数组)。回顾一下这个做法的思想,用一个线段树去维护询问可能出现的权值的信息,在这个问题中是动态维护 [ 1 , c u r − 1 ] [1,cur-1] [1,cur1]这段区间内权值在一定范围中的权值个数。例如 c u r cur cur的权值是 a c u r a_{cur} acur,那么只需要求出 [ 1 , c u r − 1 ] [1,cur-1] [1,cur1]内权值在 [ a c u r + 1 , m a x i ∈ 1 , 2 , . . n { a i } ] [a_{cur}+1,max_{i\in{1,2,..n}}\{a_i\}] [acur+1,maxi1,2,..n{ai}]中的权值个数,即为 c u r cur cur为逆序对第一个数时的逆序对个数,求和即为答案。由于每次需要调用的都是 [ 1 , c u r − 1 ] [1,cur-1] [1,cur1]区间内的权值信息,所以只需要动态维护即可。将这个思想拓展,如果每次需要调用的是任意区间内的权值信息,这时候就可以用可持久化权值线段树去维护。
例如很常见的查询区间内第k大值的做法,第 i i i棵线段树储存了 [ 1 , i ] [1,i] [1,i]区间上的权值信息,查询 [ l , r ] [l,r] [l,r]区间时只需要将第 r r r棵线段树与第 l l l棵线段树上的信息做差即可。由于从第 i i i棵线段树更新到第 i + 1 i+1 i+1棵线段树只更新了一条链上的信息,所以可以用可持久化权值线段树来维护。
需要注意的是,可持久化权值线段树不一定维护的是一个区间,只要是一段序列即可。

例如Count on a tree
题意:
给定一个包含 N N N个结点的树. 树节点从 1 1 1 N N N编号.。每个节点有一个整数权值。需要查询结点 u u u到结点 v v v的最短路径上的第 k k k小值。
思路:
设第 r t [ i ] rt[i] rt[i]棵线段树维护的是结点 i i i到根节点这一段路径上的权值信息,那么第 r t [ i + 1 ] rt[i+1] rt[i+1]棵线段树在第 r t [ f a [ i ] ] rt[fa[i]] rt[fa[i]]棵线段树上更新即可。类似于求区间第k小值的做法,将原先的第 r r r棵与第 l l l棵线段树作差改为第 r t [ l ] rt[l] rt[l]棵加上第 r t [ r ] rt[r] rt[r]棵再减去第 r t [ l c a ( l , r ) ] rt[lca(l,r)] rt[lca(l,r)]棵与第 r t [ f a [ l c a ( l , r ) ] ] rt[fa[lca(l,r)]] rt[fa[lca(l,r)]]棵即可。
AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
#define rep(i,s,t) for(int i=(s);i<=(t);i++)
#define per(i,s,t) for(int i=(s);i>=(t);i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
#define Riii(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define De(x) cout<<"#Debug "<<x<<'\n'
#define Dee(x,y) cout<<"#Debug "<<x<<' '<<y<<'\n'
#define pb push_back
#define ls(x) tr[x].ls
#define rs(x) tr[x].rs
#define val(x) tr[x].val
#define mk(x) tr[x].mk
const int N=2e6+5,V=N<<5; // 可持久化线段树一般开N<<5; 
vector<int>son[N];
int fa[N];

struct Node{
	int val,ls,rs,mk;
}tr[V];
int rt[N],a[N],b[N],c[N],ori[N],d,n,m,k,cnt=1;

void upd(int x,int p,int q,int l=1,int r=d){ 
	ls(q)=ls(p),rs(q)=rs(p),val(q)=val(p)+1;
	if(l<r){
		int mid=(l+r)>>1;
		if(x<=mid) ls(q)=++cnt,upd(x,ls(p),ls(q),l,mid);
		else rs(q)=++cnt,upd(x,rs(p),rs(q),mid+1,r);
	}
}

int tot=0,el[N<<1],mm[N<<1][30],dd[N],pos[N];
void dfs(int x,int di,int f){
	dd[x]=di;
	el[++tot]=x;
	pos[x]=tot;
	rt[x]=++cnt;
	fa[x]=f;
	upd(b[x],rt[f],rt[x]);
	for(int i=0;i<son[x].size();i++){
		if(son[x][i]==f) continue;
		dfs(son[x][i],di+1,x);
		el[++tot]=x;
	}
}
int qmin(int x,int y){return dd[x]<dd[y]?x:y;}
void rmq(){
	rep(i,1,tot)
		mm[i][0]=el[i];
	int upi=log(tot*1.0)/log(2.0);
	rep(i,1,upi){
		int upj=tot+1-(1<<i);
		rep(j,1,upj)
			mm[j][i]=qmin(mm[j][i-1],mm[j+(1<<(i-1))][i-1]);
	}
}
int lca(int l,int r){
	if(l>r) swap(l,r); 
	int tmp=log((r-l+1)*1.0)/log(2.0);
	return qmin(mm[l][tmp],mm[r-(1<<tmp)+1][tmp]);
}

void discretize(){ // 离散化 
	rep(i,1,n) c[i]=a[i];
	sort(c+1,c+n+1);
	d=unique(c+1,c+n+1)-c;
	rep(i,1,n){
		b[i]=lower_bound(c+1,c+d,a[i])-c;
		ori[b[i]]=a[i];
	}
}

int kth(int k,int p,int q,int f,int g,int l=1,int r=d){
	if(l==r) return ori[l];
	int mid=(l+r)>>1;
	if(val(ls(q))+val(ls(p))-val(ls(f))-val(ls(g))>=k) return kth(k,ls(p),ls(q),ls(f),ls(g),l,mid);
	else return kth(k-val(ls(q))-val(ls(p))+val(ls(f))+val(ls(g)),rs(p),rs(q),rs(f),rs(g),mid+1,r);
}
int main(){
	Rii(n,m);
	rep(i,1,n) 
		Ri(a[i]);
	discretize();
	rt[0]=1;
	int u,v;
	rep(i,1,n-1){
		Rii(u,v);
		son[u].pb(v);
		son[v].pb(u);
	}
	dfs(1,1,0);
	rmq();
	rep(i,1,m){
		int l,r,k;
		Riii(l,r,k);
		printf("%d\n",kth(k,rt[l],rt[r],rt[lca(pos[l],pos[r])],rt[fa[lca(pos[l],pos[r])]]));
	}
	return 0;
}

另一种用法是依然利用线段树维护区间上的信息,但对于权值序列进行更新。
例如Sign on Fence
题意:
给定一个长度为 N N N的序列 { a n } \{a_n\} {an}。给定 M M M次询问。
每次查询有三个数 l , r , w l,r,w l,r,w,表示在区间 [ l , r ] [l,r] [l,r]中,对于连续的长为 w w w的一段中最小的数字的最大值是多少。
思路:
首先将 { a n } \{a_n\} {an}排序后离散化,对于第 i i i棵线段树,维护区间上前 i i i个数的个数,再二分答案即可。
AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
#define rep(i,s,t) for(int i=(s);i<=(t);i++)
#define per(i,s,t) for(int i=(s);i>=(t);i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
#define Riii(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define De(x) cout<<"#Debug "<<x<<'\n'
#define Dee(x,y) cout<<"#Debug "<<x<<' '<<y<<'\n'
#define pb push_back

#define ls(x) LL[x]
#define rs(x) RR[x]
#define len(x) tr[x].len
#define dl(x) tr[x].dl
#define dr(x) tr[x].dr
#define seg(x) tr[x].seg
const int N=1e5+5,V=N<<5; // 可持久化线段树一般开N<<5; 
struct Node{
	int len,dl,dr,seg;
}tr[V];
int rt[N]={1},n,m,cnt=1,LL[V],RR[V];
struct Num{
	int x,y;
}a[N];
Node operator+(Node x,Node y){
	Node z;
	z.seg=x.seg+y.seg;
	if(x.dl==x.seg) z.dl=x.dl+y.dl;
	else z.dl=x.dl;
	if(y.dr==y.seg) z.dr=x.dr+y.dr;
	else z.dr=y.dr;
	z.len=max(x.len,y.len);
	z.len=max(x.dr+y.dl,z.len);
	return z;
}
void build(int p=1,int l=1,int r=n){
	if(l>r) return;
	seg(p)=r-l+1;
	if(l<r){
		ls(p)=++cnt;
		rs(p)=++cnt;
		int mid=(l+r)>>1;
		build(ls(p),l,mid);
		build(rs(p),mid+1,r);
	}
}
void upd(int x,int p,int q,int l=1,int r=n){ 
	ls(q)=ls(p),rs(q)=rs(p);
	if(l<r){
		int mid=(l+r)>>1;
		if(x<=mid) ls(q)=++cnt,upd(x,ls(p),ls(q),l,mid);
		else rs(q)=++cnt,upd(x,rs(p),rs(q),mid+1,r);
		tr[q]=tr[ls(q)]+tr[rs(q)];
	}
	else if(l==r)
		dl(q)=dr(q)=len(q)=seg(q)=1;
}
Node query(int L,int R,int p,int l=1,int r=n){
	if(L<=l&&r<=R) return tr[p];
	Node tmp={0,0,0,0};
	int mid=(l+r)>>1;
	if(R<=mid) return query(L,R,ls(p),l,mid);
	else if(L>=mid+1) return query(L,R,rs(p),mid+1,r);
	else {
		Node u=query(L,R,ls(p),l,mid);
		Node v=query(L,R,rs(p),mid+1,r);
		return u+v;
	}
}
bool cmp(Num x,Num y){
	return x.x!=y.x?x.x>y.x:x.y>y.y;
}
int l,r,w;
int main(){
	Ri(n);
	build();
	rep(i,1,n){
		Ri(a[i].x);
		a[i].y=i;
	}
	sort(a+1,a+n+1,cmp);
	rep(i,1,n){
		rt[i]=++cnt;
		upd(a[i].y,rt[i-1],rt[i]);
	}
	Ri(m);
	rep(i,1,m){
		Riii(l,r,w);
		int dl=0,dr=n;
		while(dl<dr){
			int mid=dr-(dr-dl)/2;
			if(query(l,r,rt[mid]).len>=w) dr=mid-1;
			else dl=mid;
		}
		printf("%d\n",a[dr+1].x);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值