主席树学习笔记
主席树是一种极其强大的数据结构。
记录了一颗线段树经过若干次修改的历史版本。
可以用来水各种题
原理
首先,主席树是线段树的历史版本。
即可持久化线段树。
就是每次操作后的线段树的节点信息。
这些节点信息若每次操作后都保留下来。
对空间和时间的消耗都是巨大的。
因此,我们需要可持久化线段树,主席树来储存。
我们知道,线段树的左右儿子的编号是该节点编号的两倍和两倍加一。
结构如下图所示:
对于一个已知的叶子节点,与他有关的节点只有\(logn\)个,即树高。
因此,每次修改后,只有\(logn\)个节点发生了改变。
那我们为什么要把所有的节点都复制一遍呢?
我们可以共有不会改变的节点,以减少空间以及时间开销。
那么,我们该如何共用节点同时又改变信息呢。
我们只用新建一些会发生改变的信息的节点,并把记录该次操作后的根节点。
如图所示,蓝色节点为原有节点,而红色节点为新建节点,结合红色节点和蓝色节点我们可以求出完整为线段树。
由图可见,主席树的左右儿子不再是原本线段树的左右儿子了。
所以,我们在新建一个节点的同时,要继承上一次操作的节点的所有信息,然后在修改当前的信息。
同时,对于一般的线段树,我们节点维护的信息一般都是序列的值(非权值)。
而线段树要维护历史版本的信息,因此,我们不能利用普通的线段树维护信息的方式去维护。
我们去维护的是一颗权值线段树(由于主席树是权值线段树,我们可以直接在树上二分,减掉一个\(log\)的时间复杂度。),即线段树上维护的是权值的个数。
这样,线段树就可以加减了。。。
满足了可加减后,我们可以干很多事情。
维护区间\([L,R]\)可以转化为两个前缀和相减。
大部分的题都可以解决了。
题目
说到序列上的操作不得不提起经典的区间第\(k\)值。
题目中给出了一些区间,要求求出这些区间的第\(k\)值。
先来回忆一下,一个序列的第\(k\)值是怎么求的。
我们可以二分我们要求的第\(k\)值,我们只要\(check\)即可。
我们该如何去\(check\)呢?既然他是第\(k\)值,那小于等于他的数就有\(k\),按照这个二分即可。
同时主席树是权值线段树,我们可以直接在主席树上二分时间复杂度\(O(logn)\),完美解决。
查询区间第\(k\)值函数如下:
int query(int pre,int nxt,int L,int R,int k) {
if(L==R)return L;
int mid=(L+R)>>1;
int Sum=tot[Ls[nxt]]-tot[Ls[pre]];//左儿子区间节点数量
if(Sum<k)return query(Rs[pre],Rs[nxt],mid+1,R,k-Sum);//递归到右儿子
else return query(Ls[pre],Ls[nxt],L,mid,k);//递归到左儿子
}
代码如下
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
inline int Read(void) {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a, T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
return a<b?a=b,1:0;
}
const int N=1e5+5,M=2e5+5,mod=1e9+7;
bool MOP1;
struct Function_SEG {
int cnt,Ls[N*20],Rs[N*20],tot[N*20];
void Insert(int &Now,int pre,int L,int R,int x) {
Now=++cnt;
Ls[Now]=Ls[pre];
Rs[Now]=Rs[pre];
tot[Now]=tot[pre]+1;
if(L==R)return;
int mid=(L+R)>>1;
if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
else Insert(Rs[Now],Rs[pre],mid+1,R,x);
tot[Now]=tot[Ls[Now]]+tot[Rs[Now]];
}
int query(int pre,int nxt,int L,int R,int k) {
if(L==R)return L;
int mid=(L+R)>>1;
int Sum=tot[Ls[nxt]]-tot[Ls[pre]];
if(Sum<k)return query(Rs[pre],Rs[nxt],mid+1,R,k-Sum);
else return query(Ls[pre],Ls[nxt],L,mid,k);
}
} tr;
int A[N],B[N],root[N];
bool MOP2;
void _main(void) {
int n=Read(),m=Read();
rep(i,1,n)A[i]=B[i]=Read();
sort(B+1,B+n+1);
int len=unique(B+1,B+n+1)-B-1;
rep(i,1,n) {
A[i]=lower_bound(B+1,B+len+1,A[i])-B;
tr.Insert(root[i],root[i-1],1,len,A[i]);
}
while(m--) {
int L=Read(),R=Read(),k=R-L+2-Read();
int Now=tr.query(root[L-1],root[R],1,len,k);
printf("%d\n",B[Now]);
}
}
signed main() {
_main();
return 0;
}
我们需要求出区间不重复元素和。
我们来回忆一下离线的做法,利用树状数组维护所有的排序好的区间。
所以对于一个右端点,我们要保证从1到该端点的元素和没有重复的元素,于是我们仿照树状数组的做法对于一个相同的元素在之前减掉,同时建一颗主席树即可。
这样我们就可以在线的解决掉这个问题了。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
inline int Read() {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a,T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
return a<b?a=b,1:0;
}
const int N=30005;
int n,m,cnt,root[N];
struct HJT {
int L,R,Lson,Rson;
LL tot;
} Tree[N*40];
inline void up(int num) {
Tree[num].tot=Tree[Tree[num].Lson].tot+Tree[Tree[num].Rson].tot;
}
int build(int L,int R) {
int p=++cnt;
Tree[p].L=L,Tree[p].R=R,Tree[p].tot=0;
if(L==R)return p;
int mid=(L+R)>>1;
Tree[p].Lson=build(L,mid);
Tree[p].Rson=build(mid+1,R);
return p;
}
int update(int old,int pos,int val) {
int p=++cnt;
Tree[p]=Tree[old];
if(Tree[p].L==Tree[p].R) {
Tree[p].tot+=1ll*val;
return p;
}
int mid=(Tree[p].L+Tree[p].R)>>1;
if(pos<=mid)Tree[p].Lson=update(Tree[old].Lson,pos,val);
else Tree[p].Rson=update(Tree[old].Rson,pos,val);
up(p);
return p;
}
LL Sum(int x,int L,int R) {
if(Tree[x].L==L&&Tree[x].R==R)return Tree[x].tot;
int mid=(Tree[x].L+Tree[x].R)>>1;
LL Ans=0;
if(R<=mid)Ans+=Sum(Tree[x].Lson,L,R);
else if(L>mid)Ans+=Sum(Tree[x].Rson,L,R);
else Ans+=Sum(Tree[x].Lson,L,mid)+Sum(Tree[x].Rson,mid+1,R);
return Ans;
}
int A[N],B[N],pre[N];
int main() {
int T=Read();
while(T--) {
cnt=0;
memset(pre,-1,sizeof pre);
n=Read();
root[0]=build(1,n);
rep(i,1,n)A[i]=B[i]=Read();
sort(B+1,B+n+1);
int len=unique(B+1,B+n+1)-B-1;
rep(i,1,n) {
int x=A[i],id=lower_bound(B+1,B+len+1,A[i])-B;
if(pre[id]==-1)root[i]=update(root[i-1],i,x);
else {
int Now=update(root[i-1],pre[id],-x);
root[i]=update(Now,i,x);
}
pre[id]=i;
}
m=Read();
rep(i,1,m) {
int l=Read(),r=Read();
printf("%lld\n",Sum(root[r],l,r));
}
}
}
同时,主席树不一定是只能执行序列上的操作。
能够执行很多树上以及图上的操作。
大部分有前缀性质的都可以执行。
像如果在树上前缀和,主席树的相减就要用到树上差分的思想相减。
来看看这道题
如此相似的区间第\(k\)值,那咋办呀?
我们在询问区间的第\(k\)值时,要去维护一个序列的前缀,同样,树上的一条路径是唯一的一条路径。
相似的,我们可以像维护序列的方法去维护一段树上的前缀和。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
inline int Read() {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a,T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
return a<b?a=b,1:0;
}
const int N=1e5+5;
int n,m,cnt,Case,root[N];
struct HJT {
int Lson,Rson,tot;
} Tree[N*40];
int build(int L,int R) {
int p=++cnt;
Tree[p].tot=0;
if(L==R)return p;
int mid=(L+R)>>1;
Tree[p].Lson=build(L,mid);
Tree[p].Rson=build(mid+1,R);
return p;
}
int update(int old,int L,int R,int pos,int val) {
int p=++cnt;
Tree[p]=Tree[old];
if(L==R) {
Tree[p].tot+=val;
return p;
}
int mid=(L+R)>>1;
if(pos<=mid)Tree[p].Lson=update(Tree[old].Lson,L,mid,pos,val);
else Tree[p].Rson=update(Tree[old].Rson,mid+1,R,pos,val);
Tree[p].tot=Tree[Tree[p].Lson].tot+Tree[Tree[p].Rson].tot;
return p;
}
int A[N],B[N],Fa[N][20],dep[N],len;
vector<int>edge[N];
void dfs(int x,int pre) {
Fa[x][0]=pre,dep[x]=dep[pre]+1;
root[x]=update(root[pre],1,len,A[x],1);
ret(i,0,edge[x].size()) {
int y=edge[x][i];
if(y==pre)continue;
dfs(y,x);
}
}
inline int up(int x,int step) {
rep(i,0,19)if(step&(1<<i))x=Fa[x][i];
return x;
}
inline int lca(int a,int b) {
if(dep[a]>dep[b])swap(a,b);
b=up(b,dep[b]-dep[a]);
if(a==b)return a;
drep(i,19,0)if(Fa[a][i]!=Fa[b][i])a=Fa[a][i],b=Fa[b][i];
return Fa[a][0];
}
int ask(int x,int y,int z,int p,int l,int r,int kth) {
if(l==r)return l;
int mid=(l+r)>>1;
int ltot=Tree[Tree[x].Lson].tot+Tree[Tree[y].Lson].tot-Tree[Tree[z].Lson].tot-Tree[Tree[p].Lson].tot;
if(kth<=ltot)return ask(Tree[x].Lson,Tree[y].Lson,Tree[z].Lson,Tree[p].Lson,l,mid,kth);
else return ask(Tree[x].Rson,Tree[y].Rson,Tree[z].Rson,Tree[p].Rson,mid+1,r,kth-ltot);
}
int main() {
n=Read(),m=Read();
rep(i,1,n)A[i]=B[i]=Read();
sort(B+1,B+n+1);
len=unique(B+1,B+n+1)-B-1;
rep(i,1,n)A[i]=lower_bound(B+1,B+len+1,A[i])-B;
root[0]=build(1,len);
ret(i,1,n) {
int u=Read(),v=Read();
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1,0);
rep(j,1,19)rep(i,1,n)Fa[i][j]=Fa[Fa[i][j-1]][j-1];
rep(i,1,m) {
int u=Read(),v=Read(),k=Read(),z=lca(u,v);
int Ans=ask(root[u],root[v],root[z],root[Fa[z][0]],1,len,k);
printf("%d\n",B[Ans]);
}
}
区间修改的主席树。
注意:主席树区间修改要标记永久化,不能延迟标记
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define reg register
#define debug(x) cerr<<#x<<" = "<<x<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
inline int Read() {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a,T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a,T const&b) {
return a<b?a=b,1:0;
}
const int N=100005;
int n,m,cnt,Case,root[N],A[N];
struct HJT {
int Lson,Rson;
LL tot,laze;
} Tree[N*25];
int build(int L,int R) {
int p=++cnt;
Tree[p].tot=0;
if(L==R) {
Tree[p].tot+=1ll*A[L];
return p;
}
int mid=(L+R)>>1;
Tree[p].Lson=build(L,mid);
Tree[p].Rson=build(mid+1,R);
Tree[p].tot=Tree[Tree[p].Lson].tot+Tree[Tree[p].Rson].tot;
return p;
}
int update(int old,int l,int r,int L,int R,int val) {
int p=++cnt;
Tree[p]=Tree[old];Tree[p].tot+=1ll*val*(R-L+1);
if(l==L&&R==r) {
Tree[p].laze+=val;
return p;
}
int mid=(l+r)>>1;
if(R<=mid)Tree[p].Lson=update(Tree[old].Lson,l,mid,L,R,val);
else if(L>mid)Tree[p].Rson=update(Tree[old].Rson,mid+1,r,L,R,val);
else Tree[p].Lson=update(Tree[old].Lson,l,mid,L,mid,val),Tree[p].Rson=update(Tree[old].Rson,mid+1,r,mid+1,R,val);
return p;
}
LL Sum(int x,int l,int r,int L,int R) {
if(l==L&&r==R)return Tree[x].tot;
int mid=(l+r)>>1;
LL Ans=1ll*Tree[x].laze*(R-L+1);
if(R<=mid)return Ans+Sum(Tree[x].Lson,l,mid,L,R);
else if(L>mid)return Ans+Sum(Tree[x].Rson,mid+1,r,L,R);
else return Ans+Sum(Tree[x].Lson,l,mid,L,mid)+Sum(Tree[x].Rson,mid+1,r,mid+1,R);
}
int tim;
char S[2];
int main() {
n=Read(),m=Read();
rep(i,1,n)A[i]=Read();
root[0]=build(1,n);
rep(i,1,m) {
scanf("%s",S);
if(S[0]=='C') {
int L=Read(),R=Read(),d=Read();
root[++tim]=update(root[tim-1],1,n,L,R,d);
}
if(S[0]=='Q') {
int L=Read(),R=Read();
printf("%lld\n",Sum(root[tim],1,n,L,R));
}
if(S[0]=='H') {
int L=Read(),R=Read(),T=Read();
printf("%lld\n",Sum(root[T],1,n,L,R));
}
if(S[0]=='B')tim=Read();
}
}
之前我们所遇到的第\(k\)值都是静态的。
现在我们遇到的是动态区间第\(k\)值,我们需要换一种方法。
我们对于维护的前缀和利用树状数组分割成\(log_n\)块子序列,利用分块的思想每次将以此操作分解成\(log_n\)次小操作。
对于一次查询操作,我们将两个左右端点的\(log_n\)个有关节点加到两边,每次对于两边的前缀和进行统计操作。
同样的对于每次修改操作,我们也将\(log_n\)个节点的主席树给改变掉。
同时,由于这道题卡内存,我们不能正常的利用线段树去统计。
于是,我们要开两个主席树去维护它,一颗是维护原序列,一颗是代表操作所需的主席树。
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <assert.h>
#include <algorithm>
using namespace std;
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
inline int Read(void) {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a, T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
return a<b?a=b,1:0;
}
const int N=5e4+5,M=1e4+5,mod=1e9+7;
bool MOP1;
int n,m,A[N],B[N<<1],root[N],Root[N],cnt1,cnt2,Lt[N],Rt[N],len;
struct Function_SEG {
int cnt,Ls[N*50],Rs[N*50],tot[N*50];
inline void clear(void) {
cnt=0,clr(tot,0);
}
void Insert(int &Now,int od,int L,int R,int x,int v) {
Now=++cnt;
Ls[Now]=Ls[od],Rs[Now]=Rs[od],tot[Now]=tot[od]+v;
if(L==R)return;
int mid=(L+R)>>1;
if(x<=mid)Insert(Ls[Now],Ls[od],L,mid,x,v);
else Insert(Rs[Now],Rs[od],mid+1,R,x,v);
}
int query(int rt,int lt,int L,int R,int k) {
if(L==R)return L;
int mid=(L+R)>>1,Sum=tot[Ls[rt]]-tot[Ls[lt]];
rep(i,1,cnt1)Sum-=tot[Ls[Lt[i]]];
rep(i,1,cnt2)Sum+=tot[Ls[Rt[i]]];
if(Sum<k) {
rep(i,1,cnt1)Lt[i]=Rs[Lt[i]];
rep(i,1,cnt2)Rt[i]=Rs[Rt[i]];
return query(Rs[rt],Rs[lt],mid+1,R,k-Sum);
} else {
rep(i,1,cnt1)Lt[i]=Ls[Lt[i]];
rep(i,1,cnt2)Rt[i]=Ls[Rt[i]];
return query(Ls[rt],Ls[lt],L,mid,k);
}
}
} tr;
struct Que {
int op,L,R,K;
} Q[N];
char S[5];
bool MOP2;
void _main(void) {
int T=Read();
while(T--) {
clr(root,0),clr(Root,0);
tr.clear();
n=Read(),m=Read();
int tot=0;
rep(i,1,n)A[i]=B[++tot]=Read();
rep(i,1,m) {
scanf("%s",S);
if(S[0]=='Q')Q[i].op=1,Q[i].L=Read(),Q[i].R=Read(),Q[i].K=Read();
else Q[i].op=2,Q[i].L=Read(),B[++tot]=Q[i].R=Read();
}
sort(B+1,B+tot+1);
len=unique(B+1,B+tot+1)-B-1;
rep(i,1,n) {
A[i]=lower_bound(B+1,B+len+1,A[i])-B;
tr.Insert(root[i],root[i-1],1,len,A[i],1);
}
rep(i,1,m) {
if(Q[i].op==2) {
Q[i].R=lower_bound(B+1,B+len+1,Q[i].R)-B;
int pos=Q[i].L,x=A[Q[i].L],y=Q[i].R;
while(pos<=n) {
tr.Insert(Root[pos],Root[pos],1,len,x,-1);
tr.Insert(Root[pos],Root[pos],1,len,y,1);
pos+=pos&-pos;
}
A[Q[i].L]=Q[i].R;
} else {
cnt1=cnt2=0;
for(reg int j=Q[i].L-1; j; j-=j&-j)Lt[++cnt1]=Root[j];
for(reg int j=Q[i].R; j; j-=j&-j)Rt[++cnt2]=Root[j];
printf("%d\n",B[tr.query(root[Q[i].R],root[Q[i].L-1],1,len,Q[i].K)]);
}
}
}
}
signed main() {
_main();
return 0;
}
还是区间第\(k\)值,由于题目中保证了任意时刻图都是一个森林。
所以\(x\)到\(y\)的路径是唯一的,我们又可以用上文的树上前缀和去维护了。。。
由于有连边操作,就相当于合并两个主席树,直接启发式合并利用倍增维护\(lca\)即可。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
inline int Read(void) {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a, T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
return a<b?a=b,1:0;
}
const int N=8e4+5,M=8e4+5,mod=1e9+7;
bool MOP1;
struct Link_list {
int Tot,Head[N],to[M<<1],Nxt[M<<1];
inline void clear(void) {
Tot=0,clr(Head,0);
}
inline void AddEdgepair(int a,int b) {
to[++Tot]=b,Nxt[Tot]=Head[a],Head[a]=Tot;
to[++Tot]=a,Nxt[Tot]=Head[b],Head[b]=Tot;
}
} G;
int A[N],B[N],root[N];
struct Function_SEG {
int cnt,Ls[N*200],Rs[N*200],tot[N*200];
void clear(void) {
cnt=0,clr(Ls,0),clr(Rs,0),clr(tot,0);
}
void Insert(int &Now,int pre,int L,int R,int x) {
Now=++cnt;
Ls[Now]=Ls[pre],Rs[Now]=Rs[pre],tot[Now]=tot[pre]+1;
if(L==R)return;
int mid=(L+R)>>1;
if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
else Insert(Rs[Now],Rs[pre],mid+1,R,x);
tot[Now]=tot[Ls[Now]]+tot[Rs[Now]];
}
int query(int x,int y,int z,int p,int L,int R,int k) {
if(L==R)return L;
int mid=(L+R)>>1;
int Sum=tot[Ls[x]]+tot[Ls[y]]-tot[Ls[z]]-tot[Ls[p]];
if(k<=Sum)return query(Ls[x],Ls[y],Ls[z],Ls[p],L,mid,k);
return query(Rs[x],Rs[y],Rs[z],Rs[p],mid+1,R,k-Sum);
}
} tr;
int Sz[N],Id[N],Fa[N][18],dep[N],len;
void dfs(int x,int pre,int tp) {
Sz[Id[x]=tp]++,Fa[x][0]=pre,dep[x]=dep[pre]+1;
rep(i,1,17)Fa[x][i]=Fa[Fa[x][i-1]][i-1];
tr.Insert(root[x],root[pre],1,len,A[x]);
erep(i,G,x) {
int y=G.to[i];
if(y==pre)continue;
dfs(y,x,tp);
}
}
inline int lca(int x,int y) {
if(dep[x]>dep[y])swap(x,y);
int step=dep[y]-dep[x];
drep(i,17,0)if(step&1<<i)y=Fa[y][i];
if(x==y)return x;
drep(i,17,0)if(Fa[x][i]!=Fa[y][i])x=Fa[x][i],y=Fa[y][i];
return Fa[x][0];
}
char S[2];
bool MOP2;
void _main(void) {
int T;
while(~scanf("%d",&T)) {
int n=Read(),m=Read(),q=Read(),Ans=0;
rep(i,1,n)root[i]=Sz[i]=Id[i]=0,A[i]=B[i]=Read();
sort(B+1,B+n+1);
len=unique(B+1,B+n+1)-B-1;
rep(i,1,n)A[i]=lower_bound(B+1,B+len+1,A[i])-B;
rep(i,1,m) {
int a=Read(),b=Read();
G.AddEdgepair(a,b);
}
rep(i,1,n)if(!Fa[i][0])dfs(i,0,i);
while(q--) {
scanf("%s",S);
int x=Read()^Ans,y=Read()^Ans;
if(S[0]=='Q') {
int k=Read()^Ans,LCA=lca(x,y);
Ans=B[tr.query(root[x],root[y],root[Fa[LCA][0]],root[LCA],1,len,k)];
printf("%d\n",Ans);
} else {
G.AddEdgepair(x,y);
if(Sz[Id[x]]>Sz[Id[y]])swap(x,y);
dfs(x,y,Id[y]);
}
}
}
}
signed main() {
_main();
return 0;
}
这道题才是真正的用到了主席树的本质。(之前的都是区间\(k\)值)
这道题若考虑整个序列上的中位数,我们可以二分求解。
对于当前二分值\(x\),我们将所有小于它的数都标为\(-1\),其余的数都标为\(1\),最后若该序列的\(tot>=0\)则表示是可行。
现在让我们拓展到一般的情况,我们可以同样对于每一个二分的值建一颗主席树,同时我们统计答案。
我们该如何统计答案呢?
左端点在区间\([a,b]\)中,右端点在区间\([c,d]\)中,所以中间的\([b+1,c-1]\)是必定有的。
让后如果要让答案最大,那就是让\(tot\)越大越好,所以我们自然是要维护\([a,b]\)最大的后缀和以及\([c,d]\)最大的前缀和。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define reg register
#define clr(a,b) memset(a,b,sizeof a)
#define Mod(x) (x>=mod)&&(x-=mod)
#define debug(x) cerr<<#x<<"="<<x<<endl;
#define debug2(x,y) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<endl;
#define debug3(x,y,z) cerr<<#x<<"="<<x<<" "<<#y<<"="<<y<<" "<<#z<<"="<<z<<endl;
#define rep(a,b,c) for(reg int a=(b),a##_end_=(c); a<=a##_end_; ++a)
#define ret(a,b,c) for(reg int a=(b),a##_end_=(c); a<a##_end_; ++a)
#define drep(a,b,c) for(reg int a=(b),a##_end_=(c); a>=a##_end_; --a)
#define erep(i,G,x) for(int i=(G).Head[x]; i; i=(G).Nxt[i])
inline int Read(void) {
int res=0,f=1;
char c;
while(c=getchar(),c<48||c>57)if(c=='-')f=0;
do res=(res<<3)+(res<<1)+(c^48);
while(c=getchar(),c>=48&&c<=57);
return f?res:-res;
}
template<class T>inline bool Min(T &a, T const&b) {
return a>b?a=b,1:0;
}
template<class T>inline bool Max(T &a, T const&b) {
return a<b?a=b,1:0;
}
const int N=2e4+5,M=8e4+5,mod=1e9+7;
bool MOP1;
int n,B[N],root[N];
struct Function_SEG {
int cnt,Ls[N*200],Rs[N*200];
struct node {
int lsum,rsum,sum;
} V[N*200];
void up(int num) {
V[num].lsum=max(V[Ls[num]].sum+V[Rs[num]].lsum,V[Ls[num]].lsum);
V[num].rsum=max(V[Rs[num]].sum+V[Ls[num]].rsum,V[Rs[num]].rsum);
V[num].sum=V[Ls[num]].sum+V[Rs[num]].sum;
}
void build(int &Now,int L,int R) {
Now=++cnt;
if(L==R) {
V[Now]=(node)<%1,1,1%>;
return;
}
int mid=(L+R)>>1;
build(Ls[Now],L,mid);
build(Rs[Now],mid+1,R);
up(Now);
}
void Insert(int &Now,int pre,int L,int R,int x) {
Now=++cnt;
Ls[Now]=Ls[pre],Rs[Now]=Rs[pre];
if(L==R) {
V[Now]=(node)<%-1,-1,-1%>;
return;
}
int mid=(L+R)>>1;
if(x<=mid)Insert(Ls[Now],Ls[pre],L,mid,x);
else Insert(Rs[Now],Rs[pre],mid+1,R,x);
up(Now);
}
node query(int Now,int L,int R,int l,int r) {
if(!Now||l>r)return (node)<%0,0,0%>;
if(L==l&&R==r)return V[Now];
int mid=(L+R)>>1;
if(r<=mid)return query(Ls[Now],L,mid,l,r);
else if(l>mid)return query(Rs[Now],mid+1,R,l,r);
else {
node lso=query(Ls[Now],L,mid,l,mid),rso=query(Rs[Now],mid+1,R,mid+1,r),An;
An.lsum=max(lso.sum+rso.lsum,lso.lsum);
An.rsum=max(rso.sum+lso.rsum,rso.rsum);
An.sum=lso.sum+rso.sum;
return An;
}
}
} tr;
inline bool check(int x,int a,int b,int c,int d) {
int tot1=tr.query(root[x],1,n,a,b).rsum;
int tot2=tr.query(root[x],1,n,b+1,c-1).sum;
int tot3=tr.query(root[x],1,n,c,d).lsum;
return (tot1+tot2+tot3)>=0;
}
struct Node {
int val,pos;
bool operator<(Node _)const {
return val<_.val;
}
} A[N];
bool MOP2;
void _main(void) {
n=Read();
rep(i,1,n)A[i].val=Read(),A[i].pos=i;
tr.build(root[1],1,n);
sort(A+1,A+n+1);
rep(i,2,n)tr.Insert(root[i],root[i-1],1,n,A[i-1].pos);
int q=Read(),Ans=0;
while(q--) {
rep(i,1,4)B[i]=(Read()+Ans)%n+1;
sort(B+1,B+4+1);
int L=1,R=n,Res=0;
while(L<=R) {
int mid=(L+R)>>1;
if(check(mid,B[1],B[2],B[3],B[4]))Res=mid,L=mid+1;
else R=mid-1;
}
Ans=A[Res].val;
printf("%d\n",Ans);
}
}
signed main() {
_main();
return 0;
}