主席树是 之前缀和形式基于权值线段树创建的可持久化线段树,可持久化指的是它保存了这棵树的全部历史版本.html
最简单的办法是:若是你输入了n个数,那么每输入一个数字a[i],就构造一棵保存了从a[1]到a[i]的权值线段树,因为只增长了logn的节点数,咱们增长改变的节点并将没有改变的子树指向该节点,这样须要的空间开销只有n*(4+logn)node
咱们能够把第j棵树和第(i-1)棵树上的每一个点的权值相减,来获得一棵新的权值线段树,而这个新的权值线段树至关因而输入了a[i]到a[j]之后获得的。ios
模板题 K-th Number数组
后面将第 k小/大 说成kth ide
解决什么问题:
给定一段区间,静态求区间kth 优化
想一想方法:spa
暴力:对于每个询问,排个序,就好了,时间复杂度O(nmlogn) .net
莫队+树状数组:树状数组能够求给定区间kth kthkth,使用二分+树状数组,具体不展开,可是多个区间的话,须要不断地进行树状数组的add/del操做,那么使用莫队来优化区间端点的移动问题,时间复杂度O((n+m)√n logn) 莫队复杂度*树状数组复杂度code
莫队+平衡树:把树状数组的部分替换成二叉查找树,用splay的一部分操做,须要用到kth操做,不用翻转标记什么的,时间复杂度O((n+m)√n logn)跟上面的同样
htm
目前想一想,也就这三种方法,各有优劣,暴力时间复杂度不行,可是能够在线
后面两种由于莫队的缘由必须离线
可是这三种方法时间都太慢,这个题目咱们须要一个O(nlogn) 的作法
因而主席树就诞生了
#include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll ; const int oo=0x7f7f7f7f ; const int maxn=1e5+7; const int mod=1e9+7; int n,m,cnt,root[maxn],a[maxn],x,y,k; struct node{ int l,r,sum; }T[maxn*25]; vector<int> v; int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } void update(int l,int r,int &x,int y,int pos){ T[++cnt]=T[y],T[cnt].sum++,x=cnt; if(l==r) return; int mid=(l+r)/2; if(mid>=pos) update(l,mid,T[x].l,T[y].l,pos); else update(mid+1,r,T[x].r,T[y].r,pos); } int query(int l,int r,int x,int y,int k){ if(l==r) return l; int mid=(l+r)/2; int sum=T[T[y].l].sum-T[T[x].l].sum; if(sum>=k) return query(l,mid,T[x].l,T[y].l,k); else return query(mid+1,r,T[x].r,T[y].r,k-sum); } int main(void){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]),v.push_back(a[i]); sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); for(int i=1;i<=n;i++) update(1,n,root[i],root[i-1],getid(a[i])); for(int i=1;i<=m;i++){ scanf("%d%d%d",&x,&y,&k); printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]); } return 0; }
参考博客:
https://blog.csdn.net/Stupid_Turtle/article/details/80445998