( 数据结构专题 )【 主席树 】
推荐阅读:https://blog.csdn.net/bestFy/article/details/78650360
推荐阅读:https://blog.csdn.net/qq_39565901/article/details/81782739
推荐阅读:https://blog.csdn.net/chenxiaoran666/article/details/81501461
让我们从权值线段树开始说起
要学主席树,我们就要先学权值线段树。
权值线段树的区间存的并不是节点信息,而是在值在某一范围内的数的个数,也就是一个桶树。
不会的可以先看这一篇,( 数据结构专题 )【 权值线段树 】
从权值线段树到主席树
知道了权值线段树,我们就可以开始尝试实现主席树了。
我们已经了解到在权值线段树中可以快速找到整个区间内第k大的数,那么我们怎么在自定义区间内找k大的值呢?
答:假设我们现在有5个数,那么我们每次插入一个数就保存当前权值线段树的状态,一共保存5个权值线段树。
然后我们发现,如果要求[2, 5]内某个范围内数的个数,就把第5棵数和第1棵数相减,的到的新树就是[2,5]范围的了,相应的就可以找区间的第k大了。
思考:那么我们没有这么多空间建树怎么办?
上图,左边黑线画的是插入了两个点的权值线段树, 右边画的是插入了三个点的权值线段树,容易看出其实只有在插入点往上的路径的值发生了变化,所以我们可以只保存这条路径的值,如左图蓝色的线,这样空间的问题就解决了,在开点的时候注意需要连那些点就好了,写代码可以辅助这张图写。
因为主席树不满足儿子节点和父亲节点2*node和2*node+1的关系,所以单独开数组记录每个节点的儿子节点。
例题1: Kth number HDU - 2665
题意:无序给n个数,找任意区间内第k小的值。
下面代码是找第k小的,求第k大的可以根据权值线段树的性质改一改就可以了。
代码:
#include <bits/stdc++.h>
#define mid (left+right)/2
using namespace std;
const int maxn=1e5+10;
int n,m,q,tot=0;
int a[maxn],b[maxn];
int T[maxn],tree[maxn*20],L[maxn*20],R[maxn*20];
// T[i]存的是第i棵树的root, tree[i]存的是正常线段树的值, l[i]存的是i号节点的左儿子
int built_tree( int left, int right )
{
int node = tot++;
if ( left<right ) {
L[node] = built_tree(left,mid);
R[node] = built_tree(mid+1,right);
}
return node;
}
int update( int pre, int left, int right, int x )
{
int node = tot++;
L[node] = L[pre]; // 必须要加
R[node] = R[pre];
tree[node] = tree[pre] + 1;
if ( left<right ) {
if ( x<=mid ) L[node] = update(L[pre],left,mid,x);
else R[node] = update(R[pre],mid+1,right,x);
}
return node;
}
int query( int node1, int node2, int left, int right, int k )
{
if ( left==right ) return left;
int lsum = tree[ L[node2] ] - tree[ L[node1] ];
if ( lsum>=k ) return query( L[node1], L[node2], left, mid, k );
else return query( R[node1],R[node2],mid+1,right,k-lsum ); // 注意是k-lsum
}
int main()
{
int listt;
cin >> listt;
while ( listt-- ) {
tot = 0; // 主席树动态开点,从0开始
memset(T,0,sizeof(T));memset(tree,0,sizeof(tree));
memset(L,0,sizeof(L));memset(R,0,sizeof(R));
cin >> n >> q;
for ( int i=1; i<=n; i++ ) {
scanf("%d",&a[i]); b[i]=a[i];
}
sort(b+1,b+1+n);
m = unique(b+1,b+1+n) - b - 1; // 离散化
T[0] = built_tree(1,m);
for ( int i=1; i<=n; i++ ) {
a[i] = lower_bound(b+1,b+1+m,a[i]) - b;
T[i] = update(T[i-1],1,m,a[i]);
}
while ( q-- ) {
int x,y,z;
scanf("%d %d %d",&x,&y,&z);
int p = query(T[x-1],T[y],1,m,z);
printf("%d\n",b[p]);
}
}
return 0;
}