【大杀器】利用划分树秒杀区间内第k大的数

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

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

原文地址:https://www.cnblogs.com/TFLS-gzr/p/10331352.html

时间: 2024-11-03 21:50:14

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

POJ 2761-Feed the dogs(划分树)求区间内第k小的数

Feed the dogs Time Limit: 6000MS   Memory Limit: 65536K Total Submissions: 17679   Accepted: 5561 Description Wind loves pretty dogs very much, and she has n pet dogs. So Jiajia has to feed the dogs every day for Wind. Jiajia loves Wind, but not the

zoj 2112 动态区间求第k大

题目大意: 动态单点更新,然后多次询问求区间内第k大 这里单个的主席树不能实现,这里采取的是树状数组套主席树 首先可以想的是将静态主席树先构建好,不去动它,这里空间复杂度就是O(nlogn),这个只要之前做过主席树的入门题的话就都不是问题 然后考虑更新的情况,这里将更新产生的前缀变化保存在树状数组中,那么每次更新都要更新logn棵树状数组上的主席树,每一棵的更新操作都是 logn次的,那么时间复杂度就是nlognlogn的只是可以承受的 之后的询问也是,预处理好用到的树状数组,然后保存到向量中,

poj 2401 划分树 求区间第k大的数

题目:http://poj.org/problem?id=2104 划分树待我好好理解下再写个教程吧,觉得网上的内容一般,,, 模板题: 贴代码: #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a) memset(a,0,sizeof(a)) const int MAXN = 1000

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

http://blog.itpub.net/31542119/viewspace-2199549/ XGBoost是boosting算法的其中一种.Boosting算法的思想是将许多弱分类器集成在一起形成一个强分类器.因为XGBoost是一种提升树模型,所以它是将许多树模型集成在一起,形成一个很强的分类器.而所用到的树模型则是CART回归树模型.讲解其原理前,先讲解一下CART回归树. 一.CART回归树 CART回归树是假设树为二叉树,通过不断将特征进行分裂.比如当前树结点是基于第j个特征值进

MHA大杀器——mysql主、从双击热备配置安装解析

MHA的主要作用: 在mysql的主从复制中,当master崩溃了,利用mha实现backup顶替崩溃的master自动切换为master继续工作,从而实现高可用. 下面介绍本次实验的环境: MHA分为manager管理节点和node节点,一般来讲最少是三台服务器,两台node节点,一台manager节点,但本次环境限制,只能使用两台,所以把manager也装在一台node节点上. 两台服务器,两个网口: IP: 10.2.16.253     10.0.0.1  node1 10.2.16.2

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

题意:从某个区间内最多选择k个数,使得和最大 思路:首先题目给定的数有负数,如果区间前k大出现负数,那么负数不选和更大,于是对于所有最优选择,负数不会出现,所以用0取代负数,问题便转化为区间的前k大数和. 划分树: [1  6  3  8  5  4  7  2] [6  8  5  7][1  3  4  2] [8  7][6  5][3  4][1  2] [8][7][6][5][4][3][2][1] 把快排的结果从上至下依次放入线段树,就构成了划分树,划分的意思就是选定一个数,把原序

Python中的网络扫描大杀器Scapy初探

Python中的网络扫描大杀器Scapy初探     最近经历了Twisted的打击,这个网络编程实在看不懂,都摸不透它的内在逻辑,看来网络编程不是那么好弄的.还好,看到了scapy,这种网络的大杀器,让我一看就爱不释手,这才是我需要的网络工具啊.Scapy的功能如此之多,以至于...我到现在还是没看懂.在官方网站也介绍的不多,后来搜了一下,有一本书Security Power Tools一书中,第六章介绍了Scapy,虽然简单,但是还是不明白,这两天一直在忙活着看Scapy.看了几个应用,比较

eBay回到中国做“倒爷”,在俄罗斯能飙50%的跨境电商能成大杀器吗?

eBay在中国已经沉寂了多年,当人们基本快要遗忘它之时,它却在今年1月,宣布与宁波国家跨境电子商务综合试验区达成了战略合作. 看起来似乎是有一个招商引资的成功,而且往往许多人看到各种巨头和国内某地达成某种战略合作之时,总是一笑置之.毕竟,这种大战略经常都是以没有然后而划上省略号的. 文/张书乐 人民网.人民邮电报专栏作家.TMT行业观察者.游戏产业时评人 但请把注意点落在"跨境电商"4个字上,这恰恰是eBay用来撬开当年攻不下的那些地区大门的大杀器. 从俄罗斯传来的大杀器,据说很有用

汉澳sinox运行大杀器virtualbox让winxp回归

汉澳sinox运行大杀器virtualbox让winxp回归 虽然sinox能运行部分windows程序,可是仍然有些windows运行不了,就算你绞尽脑汁也没有办法,因为各种原因导致不支持! 难道就这样等死吗?不用,还有办法,那就是用virtualbox虚拟机安装 winxp,让 winxp复活.在虚拟机下面运行winxp可能比直接在真机器上运行安全.当然数据安全都是一样的.但是有了 winxp我们可以等,一直等到QQforsinox,不用去乞求别人的支持.等到有一天sinox完全普及了,软件