普通的可持久化线段树的用法就是利用动态开点,在一次修改中只新建有修改的结点,从而实现可以调用某次修改前的线段树。例如在单点修改中,每次更改的最多只有线段树的一条链,则修改需要的时间复杂度与空间复杂度均为 O ( log N ) O(\log N) O(logN)。
可持久化线段树也有一些拓展用法,例如较常见的可持久化权值线段树。求逆序对个数常见的一种做法即建立权值线段树(or树状数组)。回顾一下这个做法的思想,用一个线段树去维护询问可能出现的权值的信息,在这个问题中是动态维护
[
1
,
c
u
r
−
1
]
[1,cur-1]
[1,cur−1]这段区间内权值在一定范围中的权值个数。例如
c
u
r
cur
cur的权值是
a
c
u
r
a_{cur}
acur,那么只需要求出
[
1
,
c
u
r
−
1
]
[1,cur-1]
[1,cur−1]内权值在
[
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,maxi∈1,2,..n{ai}]中的权值个数,即为
c
u
r
cur
cur为逆序对第一个数时的逆序对个数,求和即为答案。由于每次需要调用的都是
[
1
,
c
u
r
−
1
]
[1,cur-1]
[1,cur−1]区间内的权值信息,所以只需要动态维护即可。将这个思想拓展,如果每次需要调用的是任意区间内的权值信息,这时候就可以用可持久化权值线段树去维护。
例如很常见的查询区间内第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;
}