∑ni=1∫85∗∑kj=0i2 ∑ i = 1 n ∫ 5 8 ∗ ∑ j = 0 k i 2
emmm…最近入门了主席树, 感觉其实不是很难, 主要理解了就很简单了(毕竟代码这么短)
主席树的用处:
给出一个数列,求区间 l ~ r 之间的第 k 大值
主席树的概念:
利用数列中 n 个数据建立 n 棵树, 其中第 i 棵树维护 1 ~ i 这个前缀内的数据信息
第 i 棵树中的每个节点都有其所代表的区间范围(和线段树一样), 但这些节点所代表的区间范围指的是值域范围,
即每个节点维护 1 ~ i 之间的全部节点中在此至于范围内的数的个数
主席树的建立
(图片出自主席树详解 )
下面是一棵空树(在代码实现中可以不建树)
之前说的主席树的概念中, 主席树是要对每个节点 i 建立一棵维护前缀信息的树.
但是对于每一个前缀数列都建一棵树所要使用的节点是
n2logn
n
2
log
n
的.
所以… 真的如此朴素的建树的话必然会爆内存.但是我们仔细想想之后可以发现:
第 i 棵树 和 第 i-1 棵树中许多节点都是信息相同(即 在一棵树中的位置相同且维护的数值相同)的相似节点, .
那么我们可不可以考虑偷懒一点, 将上一棵树的节点直接拿过来用呢?当然是可以的, 图如下.
下面还有一张, 是没有重复利用节点的…森林吧 (图中相似节点编号相同).
将相同编号的节点合并之后是这样的:
主席树的模板
//by Judge
#include<bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
const int M=2e5+111;
inline int read(){
int x=0,f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
int n,m,q,cnt;
int a[M],b[M],t[M],sum[M<<5],L[M<<5],R[M<<5];
void update(int las,int &now,int l,int r,int x){ //插入节点
if(!now) now=++cnt; sum[now]=sum[las]+1; if(l==r) return ;
// l==r直接返回, 否则向下新建改变了的节点, 没改变的直接调用
if(x<=mid) R[now]=R[las],update(L[las],L[now],l,mid,x);
else L[now]=L[las],update(R[las],R[now],mid+1,r,x);
}
int query(int u,int v,int l,int r,int k){ //查询 u ~ v 区间内第 k 大的数离散后的值
if(l>=r) return l; int x=sum[L[v]]-sum[L[u]];
//这里与二分查找树类似
if(x>=k) return query(L[u],L[v],l,mid,k);
else return query(R[u],R[v],mid+1,r,k-x);
}
int main(){
n=read(), q=read();
for(int i=1;i<=n;++i)
b[i]=a[i]=read();
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-(b+1); //排序并去重
int x,y,k;
for(int i=1;i<=n;++i)
k=lower_bound(b+1,b+1+m,a[i])-b, //没有离散但已经起到了离散的作用
update(t[i-1],t[i],1,m,k);
while(q--)
x=read(), y=read(), k=read(),
printf("%d\n",b[query(t[x-1],t[y],1,m,k)]); //将离散前的值输出
return 0;
}
最后感谢 bzt 大佬的blog
同学们bye ( ^_^ )/~~