前言:
我们先来看一道题目,在分析题目时去深入理解主席树和整体二分算法
题目描述:
题目大意:
给你n个数,多次询问某段区间第k小的数。
输入格式:
第一行两个数n、m,n代表有多少个数,m代表询问数。
第二行n个数给出,不超过10^9。
接下来m行,每行三个数i,j,k,询问第i个数到第j个数中第k小的数是什么。
输出格式:
输出共m行,对每次询问的回答。
数据范围:
1<=n<=100000,1<=m<=5000。
主席树做法
主席树简析:
- 其实这是一道赤裸裸的静态区间求第 K 大值的主席树模板
- 主席树又叫做可持久化线段树,它支持查询历史版本,我们运用权值线段树来构造主席树
- 若只使用一般的线段树,每个区间开一个线段树,那么空间复杂度极高,对于稍微大一点的数据便不可行了
- 主席树恰恰避免了这一点(我觉得它要开的空间也很大…)我们假设每次询问一个区间,都要重新构造一棵线段树,很容易发现,前后两棵线段树,其实是有很多相似的信息的。
- 经过冷静分析,发现相邻两棵线段树的形状,仅有一条链是不一样的,所以我们可以动态开点,如果对于一个和上一次对应位置相同的节点,我们直接赋相同的信息即可,若是新链的点,新建该节点,再信息更新递归往下,这样子整体的时间复杂度就是 O(nlogn)
- 实际上主席树的形状并不是一棵严格意义的二叉树,它很像在一棵二叉树外面挂上一条一条新链
关于区间查找:
- 从上面的分析我们可以发现,对于任意的 i ,我们都建了一棵树
- 如果我们要查找 [1,i ] 中第 K 大的数,只需要判断左儿子所维护的信息是否大于我们要查找的 K ,大于则向左儿子递归,反之 K 减去这个信息的值再向右儿子递归
- 由于我们构造的主席树具有可减性,我们要找区间 [ i , j ] 的 K 大数,只需要像查找线段树一样,求出 query( j ) - query ( i - 1 ) 即可,类似于前缀和的思想
- 我们来看一下AC代码
AC代码280ms:
1 |
|
PS:这只是静态主席树,主席树的入门题,还是很好理解的
整体二分做法
浅谈整体二分:
- 整体二分是二分答案的进化版
- 设想我们有 Q 个询问,每次询问一个答案,如果我们用一般的二分答案,那么理论上的时间复杂度就是 O ( f( n )* n ),其中 f(n)表示的是做一次询问的时间复杂度,对于 Q 的值很大时,就难以接受了
- 所以我们不仅二分答案区间 l , r ,还二分询问区间 L ,R
- 我们用 solve (l , r , L , R ) 表示操作 L ~ R 的数的权值和询问的答案区间为 l , r ,每次二分答案
- 我们每次求出 mid 对询问的贡献,如果在区间内小于等于 mid 的个数(记为 cnt )不超过 K ,那么最终的答案也不超过 K ,我们把这一类询问划分到左区间
- 类似的如果 cnt 大于 K ,我们把它分到右区间,并且 cnt -= K
- 换句话说,如果这个数贡献小于等于 mid ,我们就不用考虑了
- 这样划分的层数是 logn ,算上 Q 次询问,再算上算贡献的复杂度,总的时间复杂度就是 O(QlognlogN) ,我们用树状数组维护…
- 另外整体二分是需要离线操作的,在本题中跑的要比主席树慢…但是空间要比主席树小得多
- 我们来看一下代码
AC代码730ms
1 |
|
后记
本人对整体二分的了解并不是很清晰(逃…
所以很口糊以后再补吧
另外我真菜…
联系qq: 953559040
微博: IncinblePan
洛谷: SherlockPan