最近看了一道题,大概就是给出一个序列,不断询问其子区间内第k大的数,下面是个截图

  绕了一圈没找到中文版题目,if(你是大佬) then 去看截图;else{我来解释:给出一个整数n,和一个整数m,分别表示序列元素个数和询问次数,然后输入n个数和m个询问,每个询问包含3个数,分别是区间起止点(l和r)和k,求出区间内第k大的数并输出;}这是一道很简单的模板题,怎么解决呢?小编最初想到的是打暴力,正所谓暴力出奇迹,说不定可以成功,反正不会优化,先试试吧,直接把规定区间[l,r]排一次序了,然后在查找一遍第k大的数,但是毫无疑问,绝对会超时,怎样能减少时间复杂度呢?这是就请出了二分。

  二分运用了分治的思想,不断将子区间分成两半,直到找到第k大的数,虽然很高效,但是面对这道题的多次询问,二分也只能表示手软,仍然过不了这道题。但是如果换成了线段树的结构,效率则会快很多,线段树看起来也用了分治的思想,把原来的整个序列都大约相等的长度分到左子树和右子树,不断分下去,直到全部分成叶子节点,在逐次确定下一层第k大的数在左子树还是右子树,虽然这种树能成功AC,但是并不是最优的,做题不能只讲求AC,下面就来讲一讲线段树的升级版——划分树。

什么是划分树?

  划分树是一种基于线段树的数据结构,也利用了分治的思想,却比线段树高效很多,这是为什么?因为划分树又多了一个性质:在划分时不是随意划分,也不是排序后直接划分(因为这样会破坏原有结构),而是排序后仍保持原来的相对顺序再分到左右子树。

具体实现方法:

  整个过程分为建树和查询两个阶段:

//copy来的图

1)建树:首先定义一个数组tree[30][1000]第一个维度表示层数,第二个维度表示这一层第i个数的值,用来表示这棵划分树,然后定义sorted[1000]数组,用来存储排序好的原序列,然后记录每一层前i个数有多少进入了下一层的左子树,存在toleft[30][1000]数组中,在建树中没用,但记录下来对查找时有用,用分治的思想分配左子树和右子树,将不大于中间值mid的数分配到左子树中去,大于中间值的分配到右子树中去,但有时为了左右尽可能个数相等,要把等于中间值的数两边都分配,于是定义same来存储多少等于中间值的数进入左子树,分配完毕后再递归分配左子树和右子树。身为递归,怎么也要有个出口吧,递归到叶子节点时就返回,即if(l==r) return;

2)查询:按照之前存储下的进入下一层左子树个数的数组toleft,可以计算出区间内第k大的数在当前节点的左子树还是右子树,并计算出下一层相应子树的左右边界,然后递归相应子树,同上,递归出口也是到达叶子节点时返回。详见注释……

废话不多说,代码呈上:

#include<iostream>
#include<algorithm>
using namespace std;
int tree[][],sorted[],toleft[][],n,m,ans;//tree和toleft的两个维度分别存储深度和序列,sorted存储的是排序好的序列
void buildtree(int l,int r,int dep)//构建划分树
{
if(l==r) return;//遇见叶子节点就返回
int mid=(l+r)/;//二分
int same=mid-l+;//same最终保存的是和中间值相同元素的个数,以便确定分到哪一区间
for(int i=l;i<=r;i++)
if(tree[dep][i]<sorted[mid]) same--;
int lpos=l;int rpos=mid+;//左指针和右指针,并非常用的指针,是用来保存现在各区间内元素个数
for(int i=l;i<=r;i++)
{
if(tree[dep][i]<sorted[mid])//小于中间值
tree[dep+][lpos++]=tree[dep][i];//分配到左子区间
else if(tree[dep][i]==sorted[mid]&&same>)//等于中间值且相同个数大于0
{
same--;
tree[dep+][lpos++]=tree[dep][i];//分配到右子区间
}
else tree[dep+][rpos++]=tree[dep][i];//剩下的分配到右子区间
toleft[dep][i]=toleft[dep][l-]+lpos-l;//toleft数组记录这一层前i个数有多少个进入下一层的左子区间,查询时有用
}
buildtree(l,mid,dep+);//构建左子区间(左子树)
buildtree(mid+,r,dep+);//构建右子区间(右子树)
}
int search(int L,int R,int l,int r,int dep,int k)//查询第k大的数
{
if(l==r) return tree[dep][l];//查询到符合要求的叶子节点,返回相应的值
int mid=(L+R)/;//L,R为大区间(主要是每个左子树,右子树的边界)
int cnt=toleft[dep][r]-toleft[dep][l-];//求出[l,r]区间内有多少数进入下一层左子区间
if(cnt>=k)//第k大的数对应节点在左子树
{
int newl=L+toleft[dep][l-]-toleft[dep][L-];//求出下一层第k大的数所在区间边界
int newr=newl+cnt-;
return search(L,mid,newl,newr,dep+,k);
}
else//在右子树
{
int newr=r+toleft[dep][R]-toleft[dep][r];//求出下一层第k大的数所在区间边界
int newl=newr-(r-l-cnt);
return search(mid+,R,newl,newr,dep+,k-cnt);
}
}
int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
{
cin>>tree[][i];
sorted[i]=tree[][i];
}
sort(sorted+,sorted+n+);//为sorted数组排序
buildtree(,n,);//建树
int a,b,c;
for(int i=;i<=m;i++)
{
cin>>a>>b>>c;//输入询问
cout<<search(,n,a,b,,c)<<endl;//查询第k大的数
}
return ;
}

//额~好像忘了改scanf和printf了,没过别怪我……

【大杀器】利用划分树秒杀区间内第k大的数的更多相关文章

  1. 利用划分树求解整数区间内第K大的值

    如何快速求出(在log2n的时间复杂度内)整数区间[x,y]中第k大的值(x<=k<=y)? 其实我刚开始想的是用快排来查找,但是其实这样是不行的,因为会破坏原序列,就算另外一个数组来存储 ...

  2. zoj 2112 动态区间求第k大

    题目大意: 动态单点更新,然后多次询问求区间内第k大 这里单个的主席树不能实现,这里采取的是树状数组套主席树 首先可以想的是将静态主席树先构建好,不去动它,这里空间复杂度就是O(nlogn),这个只要 ...

  3. HDU 3473 Minimum Sum (划分树求区间第k大带求和)(转)

    题意:在区间中找一个数,求出该区间每个数与这个数距离的总和,使其最小 找的数字是中位数(若是偶数个,则中间随便哪个都可)接着找到该区间比此数大的数的总和 区间中位数可以使用划分树,然后在其中记录:每层 ...

  4. 离群点检测与序列数据异常检测以及异常检测大杀器-iForest

    1. 异常检测简介 异常检测,它的任务是发现与大部分其他对象不同的对象,我们称为异常对象.异常检测算法已经广泛应用于电信.互联网和信用卡的诈骗检测.贷款审批.电子商务.网络入侵和天气预报等领域.这些异 ...

  5. 一文读懂机器学习大杀器XGBoost原理

    http://blog.itpub.net/31542119/viewspace-2199549/ XGBoost是boosting算法的其中一种.Boosting算法的思想是将许多弱分类器集成在一起 ...

  6. [NewLife.XCode]反向工程(自动建表建库大杀器)

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netstandard,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示 ...

  7. [csu/coj 1080]划分树求区间前k大数和

    题意:从某个区间内最多选择k个数,使得和最大 思路:首先题目给定的数有负数,如果区间前k大出现负数,那么负数不选和更大,于是对于所有最优选择,负数不会出现,所以用0取代负数,问题便转化为区间的前k大数 ...

  8. 使用docker-compose 大杀器来部署服务 上

    使用docker-compose 大杀器来部署服务 上 我们都听过或者用过 docker,然而使用方式却是仅仅用手动的方式,这样去操作 docker 还是很原始. 好吧,可能在小白的眼中噼里啪啦的对着 ...

  9. zoj2112 树状数组+主席树 区间动第k大

    Dynamic Rankings Time Limit: 10000MS   Memory Limit: 32768KB   64bit IO Format: %lld & %llu Subm ...

随机推荐

  1. mybatis在Mapper的xml文件中的转义字符的处理

    XML转义字符 < < 小于号 > > 大于号 & & 和 &apos; ’ 单引号 " " 双引号 用转义字符进行替换 例如 SE ...

  2. 解决nginx在记录post数据时 中文字符转成16进制的问题【转载】

    1. 问题描述 nginx 在获取post数据时候,如果是中文,则转换成16进制显示在日志文件中,如下图所示.   Paste_Image.png 日志格式为: log_format postdata ...

  3. 【NOIP】提高组2013 积木大赛

    [算法]找规律(听说还有写RMQ的www) [题解]ans+=(a[i]-a[i-1])  (i=1...n)(a[i]>a[i-1]) 后面比前面大k,说明要新叠加k个区间来达到所需高度.(看 ...

  4. python初步学习-python数据类型之number(数值)

    数据类型之 Number python number 数据类型用于存储数值. 数据类型是不容许改变的. 这就意味着如果改变 number 数据类型的值,将重新分配内存空间. python支持四种不同数 ...

  5. hdu 1879 继续畅通工程 (并查集+最小生成树)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1879 继续畅通工程 Time Limit: 2000/1000 MS (Java/Others)    ...

  6. AndroidStudio获得发布版安全码SHA1

    耗了一下午才搞定 在cmd中: 1.打开keytool的目录:即JDK的安装目录 2.输入口令: (E:\tenyears\tenyears\app是keystore文件的目录)

  7. socket 编程 TCP 实现简单聊天功能【转】

    转自:http://blog.csdn.net/liujia2100/article/details/9006479 版权声明:本文为博主原创文章,未经博主允许不得转载. 各个主要函数的功能: .so ...

  8. linux中时间精度的获取问题【转】

    转自:http://www.xuebuyuan.com/877633.html 目前项目需要,需要对时间进行基准,基准的精度在微秒.下午老刘给我说不能用do_gettimeofday因为他的精度虽然可 ...

  9. 关于IE8版本提示“不支持‘trim’此属性或者方法”的解决办法。转摘雨网络

    在js文件的前面加 String.prototype.trim = function () { return Trim(this); }; function LTrim(str) { var i; f ...

  10. P4819 [中山市选]杀人游戏

    题目描述 一位冷血的杀手潜入Na-wiat,并假装成平民.警察希望能在NN个人里面,查出谁是杀手.警察能够对每一个人进行查证,假如查证的对象是平民,他会告诉警察,他认识的人,谁是杀手,谁是平民.假如查 ...