K-th Number
Description
You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array segment.
That is, given an array a[1…n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: “What would be the k-th number in a[i…j] segment, if this segment was sorted?”
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2…5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.
Input
The first line of the input file contains n — the size of the array, and m — the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 109 by their absolute values — the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).
Output
For each question output the answer to it — the k-th number in sorted a[i…j] segment.
Sample Input
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
Sample Output
5
6
3
Orz fotile主席……
不得不说主席树这东西真是神奇啊……
说一下咱对主席树的理解吧~
这题题意是,求区间第K大的值……
然后主席树刚好就能满足这个需求~
大致做法是,对区间上所有值进行排序后离散化,然后对每个[1...n]的区间建一棵线段树…….
每个节点的l和
权值维护的是当前节点的左儿子所表示的离散化编号区间在所属线段树所代表的原区间上有几个值~
查询第k大利用前缀和思想,在普通查询的基础上把权值改为用[1...r]线段树的权值减去[1...l−1]线段树的权值,最后走到的叶子节点的左右儿子编号即为答案的离散化编号!
也就是用两棵线段树表示查询区间,比较在查询区间上编号属于当前离散区间的数的个数并递归查找,直到找到个数刚好为k的叶子结点就找到了!
好绕啊,让咱来举个栗子:
设咱有以下数:
1 7 9 2 3
离散为:
1 4 5 2 3
咱对[1...1][1...2][1...3][1...4][1...5]长度区间分别建线段树
则每棵线段树的节点信息保存的是它所代表的长度区间在当前节点的l−r离散区间上的值。
就像[1...3]树的1−2区间保存的就是在原串的[1...3]区间中离散编号在1−2区间的值的个数,也就是1,对应原串上的编号
还比如
注意到最大离散编号因为是离线,所以不可变,则每棵线段树的结构相同~
但问题来了:MLE~
注意到每棵线段树只是在上一棵线段树的基础上加入了1个数,所以很大一部分区间是不变的~
既然结构相同,何不利用上一棵线段树的一些节点呢~
于是,对于改变的节点新建,对于不改变的节点直接连到上一棵树相同位置的节点即可~
是不是很像可持久化~
没错,用的正是这种思想~
实现上,(╯‵□′)╯︵┻━┻为什么你们都用取地址和结构体建树啊!!!
(咱不是说取地址不好而是在这里实在太迷了看不懂)
看着都晕,导致咱这种蒟蒻看不懂定义研究代码研究了半天还没弄明白……
最后得到的这个版本是咱根据看得半懂不懂的定义纯脑洞出来的……写得有点奇葩请无视……
不过至少没有诡异的取地址和结构体的代码看着还是很清楚舒服的~
(大神:自己用不好取地址怪我咯)
(离散化排序的结构体不算)
提供咱的脑补思路:
由于结构问题,只能用内存池法存线段树(而不是位运算左右移系列)~
建树:用一个变量存上一棵树中对应正在建的这个位置的节点的编号,会跟着新树一起递归往下跑,以便必要时直接借走子节点~
查询:当前节点有两个,分别是
如果能脑补出来自然是最好的了~
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N=100011;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0' || '9'<ch){if(ch=='-')f=-1;ch=getchar();}
while('0'<=ch && ch<='9')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int root[N],l[N*20],r[N*20],w[N*20],tot;
struct node
{
int val,id;
bool operator<(node koishi)const
{
return val<koishi.val;
}
}a[N];
int n,m;
int ha[N];
int biu(int num,int last,int s,int t)
{
int now=++tot;
w[now]=w[last];++w[now];
l[now]=l[last];r[now]=r[last];
if(s==t)return now;
int mid=s+t>>1;
if(num<=mid)
l[now]=biu(num,l[last],s,mid);
else
r[now]=biu(num,r[last],mid+1,t);
return now;
}
int query(int i,int j,int val,int s,int t)
{
if(s==t)return s;
int nval=w[l[j]]-w[l[i]];
int mid=s+t>>1;
if(val<=nval)
return query(l[i],l[j],val,s,mid);
else return query(r[i],r[j],val-nval,mid+1,t);
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=n;i++)
{
a[i].val=read();
a[i].id=i;
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
ha[a[i].id]=i;
for(int i=1;i<=n;i++)
root[i]=biu(ha[i],root[i-1],1,n);
for(int i=1;i<=m;i++)
{
int s=read(),t=read(),v=read();
printf("%d\n",a[query(root[s-1],root[t],v,1,n)].val);
}
return 0;
}
本文介绍如何使用主席树解决区间第K大值的问题,通过离散化和线段树,实现快速查询区间内第K大的数值。文章详细解释了主席树的构造和查询过程,并提供了完整的代码示例。
496

被折叠的 条评论
为什么被折叠?



