缘起
题目已经在【1】中使用平方分割+二分答案的技巧艹了一遍, 现在使用线段树再艹一遍
分析
题目见【1】.
思想是这样的. 和【1】一样都是二分答案. 所以依旧需要将原数组排序。 然后进行二分答案. 但是和【1】不同的地方在于对于一个x, 如何快速计算<=x的数的个数(如果>=k, 则可以).
【1】中用的是平方分割. 而这里使用(点)线段树来实现快速计算. 既然要用线段树,那肯定要建树. 建树的话毫无疑问肯定是对[1,…,n]进行建树. 每个节点代表的是一个区间. 而这里的线段树和之前的线段树不同之处在于线段树上节点的数据. 原先都是某种指标——最大、最小或者求和啥的. 但是现在不是了,现在是该节点代表的数列排序之后的数列. 举个例子吧~
1 | 1 5 2 6 7 3 4 |
这样的序列. 它建立的线段树如下图(图中节点上面画的是该节点的数据)

ps: 这里补了一个8(为了好看, 但是代码中不会)
每个节点上的数据都是一个有序数列. 而且从底部往上看,其实就是归并排序的过程. 所以这种线段树又被称为归并树. 那么我们要用这棵归并树来实现快速计算a[i,j]中<=x的元素个数. 怎么做呢? 假设我们手上已经有了一棵这样的归并树. 然后考虑我们的区间[i,j]. 如果区间和节点表示的区间[l,r]是一个区间的话,则直接用节点上的数据进行二分查找得到该节点上<=x的元素个数. 否则的话,用节点的中点对区间进行划分然后递归(这种手段对于写过线段树的童鞋再清楚不过了). 就得到了此区间上<=x的元素个数. 所以我们再考虑建树. 回想归并排序的过程. 我们只需要在递归建树返回父节点的时候,将两棵子节点的数据(已经有序)merge成为有序序列(其实就是归并排序的过程). 建树的复杂度是O(nlogn)(普通线段树的建树复杂度是O(n)).
其实想想看——比平方分割好写! 平方分割虽然思想简单,但是代码实现起来比较精细, 容易出错.
1 | //#include "stdafx.h" |
归并树写起来很爽~ 一路思路清晰的一口气写完~ 极度爽!!!
ac情况
Status | Accepted |
---|---|
Time | 9469ms |
Memory | 24904kB |
Length | 2269 |
Lang | C++ |
Submitted | 2019-09-04 17:42:07 |
RemoteRunId | 20832124 |
妈蛋~ 归并树和平方分割都是慢的一批!!!
参考
【1】https://yfsyfs.github.io/2019/09/04/poj-2104-K-th-Number-%E5%B9%B3%E6%96%B9%E5%88%86%E5%89%B2/
v1.5.2