3946. K-th Number - #include <Seter> - using namespace Orz;

3946. K-th Number

Seter posted @ 2011年10月07日 10:49 in SPOJ with tags SegTree PartTree Classical , 3342 阅读

http://www.spoj.pl/problems/MKTHNUM/

ID DATE USER RESULT TIME MEM LANG
5779384 2011-10-05 04:49:51 Seter accepted
edit  run
2.54 9.8M

C

查询区间K大数……很久以前就想做了一直懒得看……前几天吃饭的时候突然弄懂了划分树然后自己YY了一个!结果在SPOJ跑了第二,POJ跑了第N……好囧啊果然是大牛都去做POJ了么……

划分树是这样的:假设原序列是A,排序后序列是RANK。试想一个线段树,每条线段[L,R]保存的是RANK[L..R]这些数在A中的顺序。然后再保存一个f[logn][n],记录第i层第j个数所在的这条线段中,第j个数左边(包括第j个)有几个数在下一层被分到左边。举个例子:

线段树(左边n/2个,右边(n+1)/2个,红色的是上一层中被分到左边的):

5 2 1 3 2
2 1 5 3 2
1 2 2 5 3
      3 5

f数组:

0 1 2 2 2
0 1 0 0 1
      0 1

先来解决ASK的问题:在第I层的某条线段[L,R]中的区间[NL,NR]找KTHNUM时如何向下转移。

首先我们需要知道KTHNUM被分到左边还是右边,容易得出[NL,NR]中分到左边的数字个数大于等于K时KTHNUM在左边,即F[I][NR]-F[I][NL-1]。如果一个数字被分到左边,那么NL=F[I][NL-1],NR变成F[I][NR]-1,K不变。分到右边的情况稍复杂,大家可以自己推算一下。


那么如何构造线段树与F数组呢?首先注意到RANK数组是不变得,所以对于线段[L,R]中位数MID就是RANK[(L+R)/2]。由于MID可能出现多次,其中一些在左边一些在右边,所以必须先遍历一遍RANK[L..R],求出有多少数小于MID。构造下一层时,如果一个数在左边,那么F[I][J]=F[I][J-1]+1,否则F[I][J]=F[I][J-1]。递归构树。

很简单罢!然后很容易发现线段树那个数组是可以滚动的(由于在ASK时可以完全甩掉线段树)……于是乎,滚乎!即ASK时只需要知道F数组就可以了。

这次先上代码再来解释一下其余方法,40行不到,不长不短,最难写的ASK又很容易调试,一般过了5组数据就说明对了:

有同学一定会问:ZKW线段树不是可以自底向上来避免递归嘛?那样会快很多呢!没错,但是一般线段树由孩子生成父亲的复杂度是O(1)的,而这种树则不同,而且在中位数有多个的情况下还不得不二分,又难写又慢。但是这个思路可以衍生出一个叫做“归并树”的东西。利用类似归并排序的方法构树,ASK时二分答案,在每条线段上再二分……汗……

还有一种离线的方法。如果用平衡树来维护[L,R]的KTHNUM,从[L1,R1]转为[L2,R2]的复杂度就是|L1-L2|+|R1-R2|,于是可以将询问读入后曼哈顿MST预处理,然后维护[L,R]的平衡树。这东西在区间不包含时每个数最多进一次出一次,是O(NlgN)的,常数比较小。区间包含时大约应该是O(NsqrtQlgN)的。我没写过……

如果要修改貌似就要树套树了……凌乱神马的……

【靠不上表格了】

划分树  时间复杂度O(NlgN+QlgN)  编程复杂度

归并树  时间复杂度O(NlgN+Qlg3N)  编程复杂度

曼哈顿MST+平衡树  时间复杂度O(QlgQ+NsqrtQlgN)  编程复杂度

树套树?????

By Seter
  • 无匹配
MyIdea 说:
2012年5月18日 08:52

莫队算法不需要用平衡树的.......直接树状数组就可以了


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter
Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee