前置知识:权值线段树。html
主席树也就是可持久化线段树,它能够干吗呢?咱们看这样一道题目。node
给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
数据范围:\(1≤N,M≤2⋅10^5,-10^9≤a_i≤10^9\)
咱们都知道权值线段树能够求全局第K大,可是不能求区间第K大,那遇到区间第K大如何处理呢?
区间?差分?对,咱们能够用差分,权值线段树差分。
假设有这样一组数据
4 5
6 2 19 8
2 2 1
3 4 1
3 4 2
1 2 2
4 4 1
咱们先离散,这是权值线段树基本操做把数列变为2,1,4,3看到有重复的也要去重。咱们看图怎么实现:这是在线开点的线段树,因此儿子序号并不必定是父亲节点序号2倍或2倍+1
这是离散事后的权值线段树(空树),红色数字表明这区间有几个数。
咱们开始加树进去,第一个数6,离散后是2,就把全部包含2的区间 个数+1,变成这样一张图。
再加入2,离散后1,把全部包含1的区间个数+1.如图:
再加入19,离散后是4,把全部包含4的区间个数+1,如图:
再加入8,离散后是3,把全部包含3的区间个数+1,如图:
建完树了,咱们每次开一颗线段树都记录下来,因此点的序号并非我图中的序号*。这样咱们获得了5颗线段树,假设看第一个询问2,2,1,咱们只须要用第2颗线段树减去第1颗线段树这样就能够获得区间[2,2]的值分布状况接下来,就能够用权值线段树的方法,求区间第K大了。
可是咱们能够发现,要建n颗线段树,那么空间复杂度变成\(n^2\),炸穿,须要优化。
容易发现,每次加入一个点,发现只会更改线段树上一条路上的值,其余的咱们能够链上之前的点。空间复杂度\(nlogn\)。完美。ios
#include<iostream> #include<algorithm> #include<cstdio> #include<cstdlib> using namespace std; typedef long long ll; ll a[2001000],hash[2010010],tot,root[2010010]; //root[],存下每一颗线段树的根 struct TREE { ll ln,rn,zhi; }t[10010100];//ln左儿子,rn右儿子,zhi表明有多少个点在这个区间 void gai(ll &node,ll last,ll l,ll r,ll x) { node=++tot;//在线开点 t[node]=t[last]; t[node].zhi++; if(l==r) return; ll mid=(l+r)/2; if(x<=mid) gai(t[node].ln,t[last].ln,l,mid,x); else gai(t[node].rn,t[last].rn,mid+1,r,x); } ll cha(ll node,ll last,ll l,ll r,ll k) { if(l==r) return a[l]; ll p=t[t[node].ln].zhi-t[t[last].ln].zhi;//差分 ll mid=(l+r)/2; if(k<=p) return cha(t[node].ln,t[last].ln,l,mid,k); else return cha(t[node].rn,t[last].rn,mid+1,r,k-p); } int main() { ll n,m,x,y,k; cin>>n>>m; for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),hash[i]=a[i]; sort(a+1,a+1+n); ll tt=unique(a+1,a+1+n)-a-1;//排序后,去重 for(ll i=1;i<=n;i++) { hash[i]=lower_bound(a+1,a+1+tt,hash[i])-a;//二分找出,这个点离散后的值。 gai(root[i],root[i-1],1,tt,hash[i]);//根据上一次获得的线段树,修改值。 } for(ll i=1;i<=m;i++) { scanf("%lld%lld%lld",&x,&y,&k); printf("%lld\n",cha(root[y],root[x-1],1,tt,k));//差分 } }
其实主席树能够带修改,详细看个人另外一篇博客。
题目连接
可持久化线段树(主席树模板)
可怜的狗狗
Count on a tree
Query on a tree III优化