主席树:
主席树是一种支持查询历史版本的线段树,需要动态开点,这里以主席树静态查找区间第K大为例(因为刚学只会这个)。
算法:
静态查找区间第K大其基本思想是构造不同前缀的权值线段树。想象一下如果我们拥有了每一个前缀的权值线段树,那么我们只要利用区间左端点和右端点权值线段树的信息就可以在权值线段树上实现二分查找。
但是每一个前缀开一个权值线段树显然空间开不下,所以要用到主席树。这里不同的版本就对应不同的前缀权值线段树,
就是
的历史版本,所作的更改也就是在新权值点更新了。
现在来讲主席树如何维护历史版本。维护历史版本最简单的思想就是每一个历史版本开一个权值线段树,但是不难发现每一次更改修改的位置有限,线段树上产生了更新的地方只有log个结点,所以主席树便运用了这个性质,它只对更新的部分新建结点,其它的部分用指针指向它的历史版本。
这是洛谷的模板题:
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a;i<=b;++i)
typedef long long ll;
using namespace std;
void File(){
freopen("luogu3834.in","r",stdin);
freopen("luogu3834.out","w",stdout);
}
template<typename T>void read(T &_){
T __=0,mul=1; char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')mul=-1;
ch=getchar();
}
while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
_=__*mul;
}
const int maxn=2e5+10;
int n,m,w[maxn],num[maxn],tot,root[maxn];
map<int,int>mp;
map<int,int>::iterator it;
struct Chairman_Tree{
#define mid ((l+r)>>1)
int root[maxn],cnt;
struct node{int lc,rc,sum;}a[maxn<<5];
void insert(int &rt,int l,int r,int x){
a[++cnt]=a[rt]; rt=cnt; ++a[rt].sum;
if(l==r)return;
if(x<=mid)insert(a[rt].lc,l,mid,x);
else insert(a[rt].rc,mid+1,r,x);
}
int query(int x,int y,int l,int r,int k){
if(l==r)return l;
int val=a[a[y].lc].sum-a[a[x].lc].sum;
if(k<=val)return query(a[x].lc,a[y].lc,l,mid,k);
else return query(a[x].rc,a[y].rc,mid+1,r,k-val);
}
}T;
void init(){
read(n); read(m);
REP(i,1,n)read(w[i]),mp[w[i]]=0;
for(it=mp.begin();it!=mp.end();++it)
num[++tot]=it->first,mp[it->first]=tot;
REP(i,1,n)w[i]=mp[w[i]];
REP(i,1,n){
root[i]=root[i-1];
T.insert(root[i],1,n,w[i]);
}
}
void work(){
int l,r,k;
REP(i,1,m){
read(l); read(r); read(k);
printf("%d\n",num[T.query(root[l-1],root[r],1,n,k)]);
}
}
int main(){
File();
init();
work();
return 0;
}