主席树就是对原数列每个前缀[1…i]建立一棵线段树,线段树的每个节点存储某个前缀[1…i]中属于区间[L,R]的数字共有多少个。为了避免MLE,必须利用好相邻两个线段树之间的差别条件:相邻两个线段树最多只有log个节点信息不同。原因在于相邻两节点只增加了一个节点的值,所以只出现在线段树的某条路径中,最多log个节点
1.建立
首先建立一棵空线段树
2.更新
按照离散值找对应值。更新叶节点只会影响到根节点到叶节点的一条路径,因此只需要修改该路径上的信息域data。可以利用历史版本线段树,只更改包含该元素的线段树区间data
3.查询
由于主席树每个节点是结构相同的线段树,因此可以相减。只需要查询线段树二叉中的一支即可,与线段树查询思想很相似
poj2104
题意:给定n个数,求[l,r]区间k大数,无修改,多组询问
解法:直接套用主席树
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
const int maxn = 100000+10;
const int maxn2 = 2000000+10;
int n,m;
int root[maxn],ls[maxn2],rs[maxn2],sz[maxn2],tot;
int num[maxn];
vector<int> li;
void Add(int l,int r,int x,int &y,int v){
y=++tot;
sz[y]=sz[x]+1;
if(l==r) return;
ls[y]=ls[x],rs[y]=rs[x];
int m=(l+r)>>1;
if(v<=m) Add(l,m,ls[x],ls[y],v);
else Add(m+1,r,rs[x],rs[y],v);
}
int query(int l,int r,int x,int y,int k){
if(l==r) return l;
int m=(l+r)>>1;
if(sz[ls[y]]-sz[ls[x]]>=k) return query(l,m,ls[x],ls[y],k);
else return query(m+1,r,rs[x],rs[y],k-(sz[ls[y]]-sz[ls[x]]));
}
void init(){
tot=0;
memset(root,0,sizeof(root)); memset(ls,0,sizeof(ls)); memset(rs,0,sizeof(rs)); memset(sz,0,sizeof(sz));
}
int main(){
//freopen("a.txt","r",stdin);
scanf("%d%d",&n,&m);
li.clear();
init();
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
li.push_back(num[i]);
}
sort(li.begin(),li.end());
li.erase(unique(li.begin(),li.end()),li.end());
for(int i=1;i<=n;i++){
int x=lower_bound(li.begin(),li.end(),num[i])-li.begin()+1;
Add(1,li.size(),root[i-1],root[i],x);
}
for(int i=1;i<=m;i++){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",li[query(1,li.size(),root[l-1],root[r],k)-1]);
}
return 0;
}
bzoj2588
题意:点权数多组询问:<u,v>链上第k小的权值
解法:按照dfs入戳给节点编号,然后建立每个节点到根节点的主席树,插入节点时,需要按照入戳大小从小到大插入,用父节点更新子节点。查找时需要用sz[u]+sz[v]-sz[lca(u,v)]-sz[fa[lca(u,v)][0]]计算
#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
#include<iostream>
#define ll long long
using namespace std;
const int maxn = 100000+10;
const int maxn2 = 2000000+10;
int n,m,num[maxn],last;
vector<int> li,g[maxn];
int root[maxn],sz[maxn2],ls[maxn2],rs[maxn2],tot;
int fa[maxn][20],d[maxn];
int id[maxn],in[maxn],inq;
void init(){
li.clear(),tot=last=inq=0;
memset(root,0,sizeof(root)); memset(sz,0,sizeof(sz)); memset(ls,0,sizeof(ls)); memset(rs,0,sizeof(rs));
memset(fa,0,sizeof(fa));
}
void dfs(int u,int f,int dep){
inq++,id[u]=inq,in[inq]=u,d[u]=dep,fa[u][0]=f;
for(int i=1;i<=17;i++)
if((1<<i)<=d[u]) fa[u][i]=fa[fa[u][i-1]][i-1];
else break;
for(int i=0;i<g[u].size();i++){
int v=g[u][i];
if(v==f) continue;
dfs(v,u,dep+1);
}
}
int lca(int u,int v){
if(d[u]<d[v]) swap(u,v);
int dd=d[u]-d[v];
for(int i=0;i<=17;i++) if((1<<i)&dd) u=fa[u][i];
for(int i=17;i>=0;i--)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
if(u==v) return u;
return fa[u][0];
}
void Add(int l,int r,int x,int &y,int v,int num){
y=++tot;
sz[y]=sz[x]+num;
if(l==r) return;
ls[y]=ls[x],rs[y]=rs[x];
int m=(l+r)>>1;
if(v<=m) Add(l,m,ls[x],ls[y],v,num);
else Add(m+1,r,rs[x],rs[y],v,num);
}
int query(int u,int v,int k){
int f=lca(u,v),ff=fa[f][0];
int l=1,r=li.size();
int a=root[id[u]],b=root[id[v]],c=root[id[f]],d=root[id[ff]];
while(l<r){
int m=(l+r)>>1;
int tans=sz[ls[a]]+sz[ls[b]]-sz[ls[c]]-sz[ls[d]];
if(tans>=k) r=m,a=ls[a],b=ls[b],c=ls[c],d=ls[d];
else l=m+1,a=rs[a],b=rs[b],c=rs[c],d=rs[d],k-=tans;
}
return li[l-1];
}
int main(){
//freopen("a.txt","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF){
init();
for(int i=1;i<=n;i++){
g[i].clear();
scanf("%d",&num[i]);
li.push_back(num[i]);
}
sort(li.begin(),li.end());
li.erase(unique(li.begin(),li.end()),li.end());
for(int i=1;i<n;i++){
int u,v; scanf("%d%d",&u,&v);
g[u].push_back(v),g[v].push_back(u);
}
dfs(1,0,0);
for(int i=1;i<=n;i++){
int u=in[i];
int x=lower_bound(li.begin(),li.end(),num[u])-li.begin()+1;
Add(1,li.size(),root[id[fa[u][0]]],root[i],x,1);
}
for(int i=1;i<=m;i++){
int u,v,k;
scanf("%d%d%d",&u,&v,&k); u^=last;
last=query(u,v,k);
printf("%d",last);
if(i!=m) printf("\n");//without it =>PE
}
}
return 0;
}