Time Limit: 20000MS | Memory Limit: 65536K | |
Total Submissions: 45330 | Accepted: 15083 | |
Case Time Limit: 2000MS |
Description
Input
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]]); } }