利用划分树求解整数区间内第K大的值
如何快速求出(在log2n的时间复杂度内)整数区间[x,y]中第k大的值(x<=k<=y)?
其实我刚开始想的是用快排来查找,但是其实这样是不行的,因为会破坏原序列,就算另外一个数组来存储,多次询问的条件下,同样满足不了。
划分树主要是针对上述问题而设计的。 划分树是一种基于线段树的数据结构,其基本思想就是将待查找的区间分为两个子区间:不大于数列中间 值的元素被分配到左儿子的子区间,简称左子区间;大于数列中间值的元素被分配到右儿子的子区间,简称右子区间。
显然,左子区间的数小于右子区间的数。建树 的时候要注意,对于被分到同一子树的元素,元素间的相对位置不能改变。例如构建整数序列[1 5 2 3 6 4 7 3 0 0]的划分树:
整数序列经排序后得到[0 0 1 2 3 3 4 5 6 7],中间值是3。划分出下一层的左子区间[1 2 3 0 0],中间值为1;下一层的由子区间[5 6 4 7 3],中间值为5;以中间值为界,划分出当前层每个区间的下层左右子区间······以此类推,直至划分出所有子区间含单个整数为止。
这里可能有人就会提问了,为什么两个子区间是这样的呢?? 其实我刚开始也这么想,下面给出解释:
1.如果有和中间值相等的元素,插入进去的前提是 小于中间值的元素不够填满一个区间,差多少就补多少和中间值相等的元素(很好理解,小于肯定优先嘛)。
2.相对位置不能改变。
下面给出一个图,还是以[1 5 2 3 6 4 7 3 0 0]为例:
如图可见,划分树是一颗完全二叉树,树叶为单元素区间。若数列含有n个整数,则对应的划分树有[log 2 n]+1层。
查找的时候通过记录进入左子树的数的个数,确定下一个查找区间,直至查找范围缩小到单元素为止。 此时,区间内的第K大值就找到了。
整个算法分两步:
1.建树-------离线构建整个查询区间的划分树
2.查找-------在划分树中查找某个子区间中的第K大值。
在查询之前,我们先离线构建整个查询区间的划分树。 建树过程比较简单,对于区间[l,r],首先通过对原数列排序找到这个区间的中间值的位置mid(mid=[(l+r)/2]),不大于中间值的数划入左子树[l,mid], 大于中间值的数划入右子树[mid+1,r] 。同时,对于第i 个数,记录在[l,i] 区间内有多少整数被划入左子树。 最后继续对它的左子树[l,mid] 和右子树[mid+1,r] 递归建树,直至划分出最后一层的叶节点为止,显然,建树过程是自上而下的。
具体实现办法:
先将读入的数据排序成sorted[] (从小到大排序) ,取区间[l,r]的中间值sorted[mid],然后遍历[l,r]区间,依次将每个整数划分到左子区间或者右子区间中去。 注意,被分到每个子树的整数是相对有序的。 注意,这里要记录区间能插入多少个和中间值相等的元素。 另外,在这个过程中,要记录一个类似前缀和的东西,即 l 到 i 这个区间内有多少整数被划分到左子树。设:
tree [p][i] ------第p层第i个位置的整数值,初始序列为tree[0][]。
sorted[]---------排序序列,即存储tree[0][]排序后的结果
toleftp[][]--------toleft[p][i],表示第p层前i个数中有多少个整数分入下一层的左子区间
lpos和rpos-----下一层左子区间和右子区间的指针
same------------当前区间等于中间值的个数
下面给出建树的具体代码:
void Build(int l,int r,int dep)//左边界 右边界 第几层
{
if(l==r) return ;
int mid=(l+r)>>;
int same=(mid-l+);//左子树可以有多少个等于mid的数
for(int i=l;i<=r;i++)
{
if(tree[dep][i]<sorted[mid])
same--;//计算可以放多少个等于mid值的数,保证相对位置不变
}
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>)
{
tree[dep+][lpos++]=tree[dep][i];
same--;
}
else
{
tree[dep+][rpos++]=tree[dep][i];
}
toleft[dep][i]=toleft[dep][l-]+lpos-l;
}
Build(l,mid,dep+);
Build(mid+,r,dep+);
}
在划分树上查询子区间[l,r]中第K大的数呢?(L<=l,r<=R)? 我们从划分树根出发,自上而下查找:
若查询至叶子(l==r),则该整数(tree[dep][l]) 为子区间[l,r]中第K大的数;否则区间[L,l-1]内有toleft[dep][l-1]个整数进入下一层的左子树,区间[L,r]内有toleft[dep][r]个整数进入下一层的左子树。显然,在查询子区间[l,r]中有cnt=toleft[dep][r]-toleft[dep][l-1]个整数进入到下一层的左子树。如果cnt>=k,则递归查询左子树(对应区间[L,mid]);否则递归查询右子树(对应区间[mid+1,R).。
难点是,如何在对应子树的大区间中计算被查询的子区间[newl,newr]?
若递归查询左子树,则当前子区间的左边是上一层[L,l-1]里的toleft[dep][l-1]-toleft[dep][L-1]个整数,由此可得到newl=L+toleft[dep][l-1]-toleft[dep][L-1]; 加上上一层查询区间[l,r]中cnt个整数,newr=newl+cnt-1(这里-1是为了处理边界问题,值得大家认真思索),即在大区间[L,mid]里查询子区间[newl,newr]中第K大值。(这里看文字有点抽象,最好看上面的图来理解)
同理,若递归查询右子树,则[l,r]中有r-l-cnt个整数进入右子树,需要计算其中第K-cnt大的整数值。关键是如何计算被查询子区间的右指针newr:
当上一层的[l,r]和[r+1,R]中有若干个整数被分配至下一层左子区间,其中[r+1,R]中有toleft[dep][R]-toleft[dep][r]个整数位于左子区间的右方,因此查询子区间的右指针移至r右方的第toleft[dep][R]-toleft[dep][r] 个位置,即newr=r+toleft[dep][R]-toleft[dep][r]。显然,左指针newl=newr-(r-l-cnt)。
下面给出具体实现的代码:
int query(int L,int R,int l,int r,int dep,int k)
{
if(l==r) return tree[dep][l];
int mid=(L+R)>>;
int cnt=toleft[dep][r]-toleft[dep][l-];
if(cnt>=k)
{
int newl=L+toleft[dep][l-]-toleft[dep][L-];
int newr=newl+cnt-;
return query(L,mid,newl,newr,dep+,k);
}
else
{
int newr=r+toleft[dep][R]-toleft[dep][r];//相当于把r挤到这个位置
int newl=newr-(r-l-cnt);
return query(mid+,R,newl,newr,dep+,kcnt);
}
}
利用划分树求解整数区间内第K大的值的更多相关文章
- 【大杀器】利用划分树秒杀区间内第k大的数
最近看了一道题,大概就是给出一个序列,不断询问其子区间内第k大的数,下面是个截图 绕了一圈没找到中文版题目,if(你是大佬) then 去看截图:else{我来解释:给出一个整数n,和一个整数m,分别 ...
- zoj 2112 动态区间求第k大
题目大意: 动态单点更新,然后多次询问求区间内第k大 这里单个的主席树不能实现,这里采取的是树状数组套主席树 首先可以想的是将静态主席树先构建好,不去动它,这里空间复杂度就是O(nlogn),这个只要 ...
- hdu 4638 树状数组 区间内连续区间的个数(尽可能长)
Group Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Subm ...
- zoj2112 树状数组+主席树 区间动第k大
Dynamic Rankings Time Limit: 10000MS Memory Limit: 32768KB 64bit IO Format: %lld & %llu Subm ...
- BZOJ 2752 [HAOI2012]高速公路(road):线段树【维护区间内子串和】
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2752 题意: 有一个初始全为0的,长度为n的序列a. 有两种操作: (1)C l r v: ...
- POJ2104 K-th Number (子区间内第k大的数字)【划分树算法模板应用】
K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 40920 Accepted: 13367 Ca ...
- [TS-A1505] [清橙2013中国国家集训队第二次作业] 树 [可持久化线段树,求树上路径第k大]
按Dfs序逐个插入点,建立可持久化线段树,每次查询即可,具体详见代码. 不知道为什么,代码慢的要死,, #include <iostream> #include <algorithm ...
- SDIBT 3237 Boring Counting( 划分树+二分枚举 )
http://acm.sdibt.edu.cn/JudgeOnline/problem.php?id=3237 Problem H:Boring Counting Time Limit: 3 Sec ...
- sdut 2610:Boring Counting(第四届山东省省赛原题,划分树 + 二分)
Boring Counting Time Limit: 3000ms Memory limit: 65536K 有疑问?点这里^_^ 题目描述 In this problem you a ...
随机推荐
- 配置PL/SQL Developer连接Oracle数据库
准备: PL/SQL Developer:我用的是plsqldev1005(32位) win32_11gR2_client:记住一定是32位的,因为PL/SQL Developer只认32位的 安装成 ...
- C# winForm 开机自动启动 并启动后最小化到任务栏 右侧通知栏并交互操作
//设置自动启动 string path = Application.StartupPath; SettingHel.SetAutoRun(path +@"\MostImpressive.D ...
- HTML完全使用详解 PDF扫描版
<HTML完全使用详解>根据网页制作的实际特点和目前市场需要,全面系统地介绍了最新的HTML4.01.丰富的实例贯穿全书,能帮助您全面掌握HTML,而且本书所有实例均可直接修改使用,可以提 ...
- Java 在本地开发环境部署多个 spring 项目
修改Tomcat 的 server.xml 文件 路径:C:\JAVA\apache_tomcat_8.5.11\conf\server.xml : 每个web项目的端口号不同,且存储的目录相同但是文 ...
- onmouseover和onmouseout在GridView中应用 Ver2
第一个版本,可以参考:http://www.cnblogs.com/insus/archive/2009/03/13/1411057.html 以前的版本,是在Gridview的OnRowCreate ...
- javascript javascript面向对象的理解及简单的示例
javascript面向对象的理解及简单的示例 零.本节重点: 1.封装: 2.继承: 壹.下面理解: 一. javascript面向对象概念: 为了说明 JavaScript 是一门彻底的面向对象的 ...
- Glib学习笔记(二)
你将学到什么 如何实现Object的构造函数和析构函数 如何在条件检测不允许的情况下终止对象创建 Object的构造函数 对象的构造函数是不允许失败,如果你需要一个允许失败的GObject构造函数,使 ...
- Zookeeper学习文档
1. Zookeeper简介 ZooKeeper是一个开源的分布式框架,提供了协调分布式应用的基本服务.它向外部应用暴露一组通用服务——分布式同步(Distributed Synchronizat ...
- JAVA 正则表达式的三种模式: 贪婪, 勉强和占有的讨论
假设待处理的字符串是 xfooxxxxxxfoo 模式.*foo (贪婪模式): 模式分为子模式p1(.*)和子模式p2(foo)两个部分. 其中p1中的量词匹配方式使用默认方式(贪婪型). 匹配开 ...
- Div的移动
//JQuery 拖拽本体DIV,把以下代码全部复制即可 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...