The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0
Your task is to write a program for this computer, which
- Reads N numbers from the input (1 <= N <= 50,000)
- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.
Input
The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.
The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format
Q i j k or
C i t
It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.
There're NO breakline between two continuous test cases.
Output
For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])
There're NO breakline between two continuous test cases.
Sample Input
2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
Sample Output
3
6
3
6
(adviser)
Site: http://zhuzeyuan.hp.infoseek.co.jp/index.files/our_contest_20040619.htm
Source: Online Contest of Christopher's Adventure
题意:
给你一个长度为n(1 <= N <= 50,000)的序列。序列中每个值都不超过1e9.然后有m(1 <= M <= 10,000)次操作。
1.Q i j k 询问i,j间第k大的值。
2.C i t 把序列的第i个值改成t。
思路:
明显的主席树。
网上关于主席树的资料比较少。所以介绍下主席树。一方面让初学者少走弯路。另一方面方便自己温习。避免过段时间自己的代码都不认识了。。。
先讲下主席树相关的概念吧。
1.什么是主席树。
主席树貌似是网上流传的一种叫法。貌似学名叫函数式线段树。相关概念可以自行百度。
2.主席树有什么用。
主席树可以求解区间第k大问题。当然这个划分树也可以完成且时间和空间都比主席树更优。那学主席树还有什么用。当然有用啦。划分树只能解决静态第k大问题。也就是说。如果序列里的值有更新的话划分树就不再适用了。这个时候就体现主席树的优势了。主席树的其他应用还没研究。等遇到了再补充。
3.主席树到底是什么。
其实所谓的主席树就是一堆线段树。一堆线段树?一颗线段树就耗那么多内存了。一堆不会爆么?这个就是主席树的精华了。后面再解释。
4.主席树是怎么实现查询区间第k大问题的呢?
先从最基本的问题入手吧。如果区间是固定的就为[1,n]。然后询问区间的第k大。很简单吧。排个序就行了,但是我们要讲的是线段树的做法。考虑到序列的值可能很大。我们可以先对这n个值hash一下映射到1-n的范围内。然后我们就把这n个值插入线段树。线段树的每个结点维护子树中已经插入值的个数。那么查找第k大就很简单了。如果k>左子树值的个数。那么就在右子树中找第(k-左子树值个数)大值。否则在右子树中找。递归进行直到到叶子结点。然后还原hash值就行了。
现在关键就是怎么解决区间不是[1,n]的问题了。假如我们要查询区间[l,r]的第k大值。如果我们有一颗线段树R插入了里面的值。当然就跟区间[1,n]的方法一样了。假如我们建了n棵上面的线段树。第i棵插入了[1,i]的值。感觉前缀的思想真的很巧妙。跟字符串的前缀有异曲同工之妙。第i棵线段树的根为T[i]。那么怎么求[l,r]的第k大呢。其实和上面方法差不多。我们关键就是要知道线段树R区间[l,r]内的值在左子树的值有多少个。在右子树有多少个。这个时候前缀的优势就来了。(T[r]左子树值的个数-T[l-1]左子树值的个数)不就是R在左子树值的个数么。然后递归定位到叶子结点就行了。
但问题又来了。那么多棵线段树。就算内存不爆。建树的时间也该爆了吧。精华部分来了。不得不叹服前人的智慧啊。由于这n棵线段树结构形态一致。结点个数都一样。这个很好理解吧。都是维护[1,n]值出现个数嘛。而更加爽的是。T[i+1]比T[i]多插一个值a[i+1].试想如果a[i+1]被插入了T[i+1]的左子树。那么T[i+1]的右子树和T[i]的左子树将完全相同。进入右子树同理。那所以T[i+1]的右子树完全不用建了。直接把T[i+1]右子树指针指向T[i]右子树就行了。共享结点。这是多么机智。一下解决了一半的结点。这样的做法递归进行。也就是T[i+1]只需建log2(n)的结点。这样就可以解决静态第k大了。
可以拿这个题开刀嘿嘿~
现在剩下的就是解决动态第k大问题了。这个地方我理解了很久。才明白。明白了其实很简单。如果我们更新了arr[i]那么它将会影响T[i]~T[n]。难道我们要一个一个改么。这样改显然时间上不允许。但是你会发现它的改变对T[i]~T[n]的影响是一样的。如果我们把影响(做子树增加多少值。右子树增加多少结点)记录下来。那我们就只需用原始值减去变化值就好了。变化值我们可以用树状数组来记录。我们把变化当作一个值,那么就成了单点更新区间求和问题了。只是这里不是一个值而是一个线段树而已,但是我们可以类似的处理。
也就是每个树状数组的结点都是一棵线段树。哈哈。真是刺激。那么这个问题就圆满解决了。分析下时空复杂度。首先建一棵空树m*log2(m)。m为hash后值的个数。然后建n个树.n*log2(m)。然后q次查询操作.2*q*log2(m).所以总时间复杂度为。O((m+n+2*q)*log2(m))。空间复杂度。4*m+n*lon2(m)。
下面附上代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include