我的主席树启蒙题目之一(之前是普通模板)
对于一棵树,我们如何使用主席树算法?
答案是像树上差分那样,以父亲为基础向儿子建立主席树,这样就可以利用树的优秀的dfs性质。
每个主席树维护的是自己到根的所有信息。
那么查询两个点x,y的信息就是经典的x+y-lca(x,y)-fa(lca(x,y) )。(可以脑补一下)
然后对于连边操作,我们使用启发式合并,将size比较小的树合并向比较大的树,对里面所有点的lca倍增数组重新赋值。
代码如下,应该可能有讲解
#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
int cnt=0,f=1;char ch=0;
while(!isdigit(ch)){
ch=getchar();if(ch=='-')f=-1;
}
while(isdigit(ch)){
cnt=cnt*10+ch-48;
ch=getchar();
}
return cnt*f;
}
const int N=80003;
int t[N],dep[N],fa[N][17],son[N],first[N];//主席树根,深度,倍增数组,子树大小,邻接表
int nxt[N*4],to[N*4],tot;int cnt;//邻接表,cnt是主席树的id用
int L[N*600],R[N*600],sum[N*600];//主席树 由于本题建立的主席树太多,请把内存开大
int n,m,q,M,a[N],b[N];int F[N];int vis[80003];//m为离散化后的size,M为初始边数,ab离散化,F并查集用,vis用于在森林中判断是否查询
void add(int a,int b){
nxt[++tot]=first[a];first[a]=tot;to[tot]=b;
}
int build(int l,int r){
int root=++cnt;
int mid=(l+r)>>1;
if(l<r){
L[root]=build(l,mid);R[root]=build(mid+1,r);
}
return root;
}
int update(int pre,int l,int r,int key){
int root=++cnt;int mid=(l+r)>>1;
R[root]=R[pre],L[root]=L[pre],sum[root]=sum[pre]+1;
if(l<r){
if(key<=mid){
L[root]=update(L[pre],l,mid,key);
}else R[root]=update(R[pre],mid+1,r,key);
}
return root;
}
void dfs(int u,int faa,int x){//dfs修改倍增数组,子树大小son,并查集F,建立主席树。
fa[u][0]=faa;
for(int i=1;i<=16;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}son[x]++;dep[u]=dep[faa]+1;vis[u]=1;
int key=lower_bound(b+1,b+n+1,a[u])-b;
F[u]=faa;
t[u]=update(t[faa],1,m,key);
for(int i=first[u];i;i=nxt[i]){
int v=to[i];if(v==faa)continue;
dfs(v,u,x);
}
}
int find(int u){
if(u==F[u])return u;
return F[u]=find(F[u]);
}
int findlca(int a,int b){
if(a==b)return a;
if(dep[a]<dep[b])swap(a,b);
for(int i=16;i>=0;i--){
if(dep[fa[a][i]]>=dep[b]){
a=fa[a][i];
}
}
if(a==b)return a;
for(int i=16;i>=0;i--){
if(fa[a][i]!=fa[b][i]){
a=fa[a][i],b=fa[b][i];
}
}
return fa[a][0];
}
int query(int a,int bb,int lca,int lcaa,int l,int r,int key){//两个点,lca,fa(lca),左,右,查询第key大
if(l==r)return b[l];
int mid=(l+r)>>1;
int sz=sum[L[a]]+sum[L[bb]]-sum[L[lca]]-sum[L[lcaa]];//x+y-lca-fa(lca)
if(key<=sz){
return query(L[a],L[bb],L[lca],L[lcaa],l,mid,key);
}
else return query(R[a],R[bb],R[lca],R[lcaa],mid+1,r,key-sz);
}
int main(){
n=in;n=in;M=in;q=in;
for(int i=1;i<=n;i++){
a[i]=b[i]=in;
}sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=M;i++){
int x=in;int y=in; add(x,y);add(y,x);
}
t[0]=build(1,m);
for(int i=1;i<=n;i++){
if(!vis[i]){//森林判断
dfs(i,0,i);
}
F[i]=i;//甩进来一起搞定
}int lastans=0;
char ch[3];int x,y,z;
while(q--){
scanf("%s",ch);
x=in;y=in;x=x^lastans,y=y^lastans;
if(ch[0]=='Q'){
z=in;z=z^lastans;
int lca=findlca(x,y);
lastans=query(t[x],t[y],t[lca],t[fa[lca][0]],1,m,z);
printf("%d\n",lastans);
}
else{
add(x,y);add(y,x);
int u=find(x),v=find(y);
if(son[x]<son[y]){//保证x,u对应比较大的树
swap(u,v);swap(x,y);
}
dfs(y,x,u);//对小的树重新修改
}
}
return 0;
}