Description
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 second line contains n different integer numbers not exceeding 10 9 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
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
Hint
Source
Northeastern Europe 2004, Northern Subregion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
主席树~
求区间第k大,无修改的~
关于主席树的介绍,找了个超级棒的,转自http://prominences.weebly.com/1/post/2013/02/1.html,稍稍修改了一点,不要打我啊!(逃
可持久化线段树,也叫作函数式线段树,也就是主席树,(因为先驱就是fotile主席。Orz)
可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。
一些数据结构,比如线段树或平衡树,他们一般是要么维护每个元素在原序列中的排列顺序,要么是维护每个元素的大小顺序,若是像二者兼得,那么,就想想主席树吧~(还可以用划分树做!)
既然叫“函数式线段树”,那么就应该有跟普通线段树相同的地方。一颗线段树,只能维护一段区间里的元素。但是,每个询问的区间都不一样,若是对每段区间都单独建立的线段树,那~萎定了~。因此,就要想,如何在少建,或建得快的情况下,利用一些方法,得出某个区间里的情况。
比如一棵线段树,记为tree[i][j],表示区间[i,j]的线段树。那么,要得到它的情况,可以利用另外两棵树,tree[1][i-1]和tree[1][j],得出来。也就是说,可以由建树的一系列历史版本推出。
那么,怎么创建这些树呢?
首先,离散化数据。因为如果数据太大的话,线段树会爆~
在所有树中,是按照当前区间元素的离散值(也就是用大小排序)储存的,在每个节点,存的是这个区间每个元素出现的次数之和(data域)。出现的次数,也就是存了多少数进来(建树时,是一个数一个数地存进来的)。
先建一棵线段树,所有的节点data域为0。再一个节点一个节点地添加。把每个数按照自己的离散值,放到树中合适的位置,然后data域+1,回溯的时候也要+1。当然,不能放到那棵空树中,要重新建树。第i棵树存的是区间(原序列)[1,i]。但是,如果是这样,那么会MLE+TLE。因此,要充分利用历史版本。用两个指针,分指当前空树和前一棵树。因为每棵树的结构是一样的,只是里面的data域不同,但是两棵相邻的树,只有一数只差,因此,如果元素要进左子树的话,右子树就会跟上个树这个区间的右子树是完全一样的,因此,可以直接将本树本节点的右子树指针接到上棵树当前节点的右儿子,这样既省时间,又省空间。
每添加一个节点(也就是新建一棵树)的复杂度是O(logn),因此,这一步的复杂度是O(nlogn)。
建完之后,要怎么查找呢?
一般的在整棵树中找第k个数的方法是:如果一个节点的左权值(左子树上点的数量之和)大于k,那么就到左子树查找,否则到右子树查找。其实主席树是一样的。对于任意两棵树(分别存区间[1,i]和区间[1,j],i<j),在同一节点上(两节点所表示的区间相同),data域之差表示的是,原序列区间[i,j]在当前节点所表示的区间里,出现多少次(有多少数的大小是在这个区间里的)。同理,对于同一节点,如果在两棵树中,它们的左权值之差大于等于k,那么要求的数就在左孩子,否则在右孩子。当定位到叶子节点时,就可以输出了。
(求编号用了lower_bound()而不是二分,居然变快了;内存是nlogn的,2*10^6就足够了~)
(最后输出的是num[i]不是i,样例里面数和序号相等,检查不出这个错。)
:)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define mid (l+r>>1)
int n,m,x,y,k,a[100001],num[100001],tot,root[100001],cnt,c[2000001],ls[2000001],rs[2000001];
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0' && ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
void build(int l,int r,int u,int &v,int k)
{
v=++cnt;c[v]=c[u]+1;
if(l==r) return;
ls[v]=ls[u];rs[v]=rs[u];
if(k<=mid) build(l,mid,ls[u],ls[v],k);
else build(mid+1,r,rs[u],rs[v],k);
}
int cal(int l,int r,int u,int v,int k)
{
if(l==r) return l;
if(c[ls[v]]-c[ls[u]]>=k) return cal(l,mid,ls[u],ls[v],k);
return cal(mid+1,r,rs[u],rs[v],k-c[ls[v]]+c[ls[u]]);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read(),num[i]=a[i];
sort(num+1,num+n+1);
tot=unique(num+1,num+n+1)-num-1;
for(int i=1;i<=n;i++) build(1,tot,root[i-1],root[i],lower_bound(num+1,num+tot+1,a[i])-num);
while(m--)
{
x=read();y=read();k=read();
printf("%d\n",num[cal(1,tot,root[x-1],root[y],k)]);
}
return 0;
}