主席树摘要
介绍&主要用途
主席树,据说由fotile大佬因不会划分树而发明。由于大佬名字缩写为HJT而被Seter大佬命名为主席树。
主要用于查询区间第k值。
原理
首先离散化,对每个节点i建一棵[1,i]的权值线段树。这里用到了前缀和思想并且满足可加减性。但这会MLE。然后我们发现,对于每个点的线段树,它和前面那个节点的线段树有且仅有logn个节点是不同的,因此我们可以借用前一个节点的线段树,仅仅新建logn个节点。这个思想成为了主席树和所有可持久化数据结构的思想基础。
变量
1 int n,m; 2 int a[maxn]; //初始数组 3 int lch[maxn],all; //离散化 4 struct Node { 5 int L,R,sum; //左儿子、右儿子编号和在此区间内的数的个数 6 } tree[maxn*16]; 7 int ins; 8 int root[maxn]; //每个节点的线段树的根
建树
整个序列直接建树有点困难(也可能是我太菜了),考虑依次插入每个数。
如果到叶子节点就在前一个点的基础上更新并直接返回,否则为相应的儿子分配新节点。
1 void insert(int now,int l,int r,int x,int pla) { //now为前一个点的当前编号,pla为分配节点编号 2 if(l==r) { 3 tree[pla].sum=tree[now].sum+1; //在前一个点的基础上更新节点 4 return; 5 } 6 int mid=l+r>>1; 7 if(x>mid) { 8 tree[pla].L=tree[now].L; //复制左儿子 9 insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins)); //新建右儿子并向右走 10 } else { 11 tree[pla].R=tree[now].R; //复制右儿子 12 insert(tree[now].L,l,mid,x,(tree[pla].L=++ins)); //新建左儿子并向左走 13 } 14 update(pla); 15 }
查询区间[l,r]第k小
利用前缀和思想,传入l-1和r,相减即可。
1 int query(int l,int r,int z,int y,int k) { 2 if(l==r) return l; //叶子节点就是答案 3 int mid=l+r>>1; 4 return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)? 5 query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)): //左边太少,往右走 6 query(l,mid,tree[z].L,tree[y].L,k); //左边太多,往左走 7 }
时空复杂度
时间复杂度
建树:O(logn)
单次查询:和线段树一样O(logn)
空间复杂度
由于每个值仅出现logn次,有n个值,因此空间复杂度为O(nlogn)
题目
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define INF 0x7fffffff 4 #define ME 0x7f 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout) 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c)) 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c)) 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i]) 9 #define ll long long 10 #define MEM(a,b) memset(a,b,sizeof(a)) 11 #define maxn (200000+10) 12 int n,m; 13 int a[maxn]; 14 int lch[maxn],all; 15 struct Node{ 16 int L,R,sum; 17 }tree[maxn*16]; 18 int ins; 19 int root[maxn]; 20 template<class T> 21 inline T read(T &n){ 22 n=0;int t=1;double x=10;char ch; 23 for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0'; 24 for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0'; 25 if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10; 26 return (n*=t); 27 } 28 template<class T> 29 T write(T n){ 30 if(n<0) putchar('-'),n=-n; 31 if(n>=10) write(n/10);putchar(n%10+'0');return n; 32 } 33 template<class T> 34 T writeln(T n){write(n);putchar('\n');return n;} 35 void update(int x){tree[x].sum=tree[tree[x].L].sum+tree[tree[x].R].sum;} 36 void insert(int now,int l,int r,int x,int pla){ 37 if(l==r){tree[pla].sum=tree[now].sum+1;return;}int mid=l+r>>1; 38 if(x>mid){tree[pla].L=tree[now].L;insert(tree[now].R,mid+1,r,x,(tree[pla].R=++ins));} 39 else{tree[pla].R=tree[now].R;insert(tree[now].L,l,mid,x,(tree[pla].L=++ins));}update(pla); 40 } 41 int query(int l,int r,int z,int y,int k){ 42 if(l==r) return l;int mid=l+r>>1; 43 return (k>tree[tree[y].L].sum-tree[tree[z].L].sum)? 44 query(mid+1,r,tree[z].R,tree[y].R,k-(tree[tree[y].L].sum-tree[tree[z].L].sum)): 45 query(l,mid,tree[z].L,tree[y].L,k); 46 } 47 int main(){ 48 read(n);read(m); 49 fui(i,1,n,1) lch[i]=read(a[i]); 50 sort(lch+1,lch+n+1);all=unique(lch+1,lch+n+1)-lch-1; 51 fui(i,1,n,1){ 52 int p=lower_bound(lch+1,lch+all+1,a[i])-lch; 53 insert(root[i-1],1,all,p,(root[i]=++ins)); 54 } 55 fui(i,1,m,1){ 56 int l,r,k; 57 read(l);read(r);read(k); 58 writeln(lch[query(1,all,root[l-1],root[r],k)]); 59 } 60 return 0; 61 }