Time Limit: 20000MS | Memory Limit: 65536K | |
Total Submissions: 45330 | Accepted: 15083 | |
Case Time Limit: 2000MS |
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
题解:这是一道主席树静态区间查询第K大的模板题
研究了一下午的主席树,总算懂得了一点眉目。
什么是主席树
可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它
们之间的共同数据来减少时间和空间消耗。
因此可持久化线段树也叫函数式线段树又叫主席树。
主席树的主体是线段树,跟准确的说是由多颗形态相同的线段树所构成的。所谓主席树就是给每个区间[1,i]都建立一颗线段树,但如果每个区间都重新构建线段树的话不仅在时间是无法承受,空间也大的惊人。于是就发现了主席树一个很好的性质,那就是每新建的一个区间都是依附前一个区间存在的,什么意思呢?就是要充分他的前一颗树。用两个指针,分指当前空树和前一棵树。因为每棵树的结构是一样的,只是某些节点控制的数目不同,但是两棵相邻的树,只有一数只差,因此,如果元素要进左子树的话,右子树就会跟上个树这个区间的右子树是完全一样的,因此,
可以直接将本树本节点的右子树指针接到上棵树当前节点的右儿子,并且对于每次更改,最多增加logn 个节点,所以空间复杂度约为nlogn ,这样即省时间,又省空间。
具体情况见下图:
在建立线段树时一般都需要记录三个值,左右子树根节点的编号以及这颗线段树中一共有多少个数。
在建树时必须要保证这颗线段树的先序遍历是升序排列的,也就是说当前这个数在原数列中是第几大,那么他在线段
树[1,n]的区间中的位置就在哪,即为下标。那有人肯定会问,如果数值很大的话,该怎么办呢?这里引入一种思想
——离散化。
何为离散化?
就是给一个数列按从小到大的顺序编号,赋予他一个新的值。
举个例子:
原数列 12 15 87 69 42
离散化后 1 2 5 4 3
有了这些铺垫,就可以用主席树求解区间第K小了
因为每棵树的形态相同,那么就可以做减法。跟一般的,在整棵树中找第k个数是一样的。如果一个节点的左权值(左
子树上点的数量之和)大于k,那么就到左子树查找,否则到右子树查找。其实主席树也一样的。对于任意两棵树(分
别存区间[1,i]和区间[1,j] i<j),在同一节点上(两节点所表示的区间相同),控制节点的个数之差表示的是,原序列
区间[i,j]在当前节点所表示的区间里,有多少数是在这个区间里的。同理,对于同一节点,如果在两棵树中,它们的点
数之差大于等于k,那么要求的数就在左孩子,否则在右孩子。当递归到叶子节点时,就可以输出了。具体的过程可以
结合代码,感性的思考一下(其实有点类似于平衡树找第K大的感觉)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100003
#define M 400003
using namespace std;
int a[N],b[N],p[N],root[N],n,m,sz;
struct data
{
int l,r,w;
};data tree[M*20];
int cmp(int x,int y)
{
return a[x]<a[y];
}
void insert(int &i,int l,int r,int x)
{
tree[++sz]=tree[i]; i=sz;
tree[i].w++;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(tree[i].l,l,mid,x);
else insert(tree[i].r,mid+1,r,x);
}
int query(int i,int j,int l,int r,int k)
{
if (l==r) return l;
int t=tree[tree[j].l].w-tree[tree[i].l].w;
int mid=(l+r)>>1;
if (t>=k) return query(tree[i].l,tree[j].l,l,mid,k);
else return query(tree[i].r,tree[j].r,mid+1,r,k-t);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
p[i]=i;
}
sort(p+1,p+n+1,cmp);
for (int i=1;i<=n;i++)
b[p[i]]=i;//离散化,p[i]表示第i小的值在a[]中的下标
sz=0; root[0]=0;
for (int i=1;i<=n;i++)
{
root[i]=root[i-1];
insert(root[i],1,n,b[i]);
}
for (int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
int t=query(root[x-1],root[y],1,n,z);
printf("%d\n",a[p[t]]);
}
}