划分树

本来是学主席树的,可怜我等巨弱观群巨博客难解fotle主席的思想精髓。于是学了一下划分树,嗯,花了一下午时间理解build(其实自己模拟一遍就通了),我很难理解为什么划分树会看不懂而能学会主席树。唉,学业有先后术业有专攻,斯已矣。其实思想不难理解,代码的话找个样例模拟一遍然后就通了。

+++划分树+++。

本人比较懒省事于是就用了二维数组式的风格。

1.sorted[MAXN]。原数组排序后的数组(参照下图第一行)

2.tree[20][MAXN]。20是划分树的层数,每层最多MAXN个元素,也就是数列的长度。可以想想为什么最多20层就够了。如果不明白,别急,往下看!

3.t_l[20][MANX]。即toleft,t_l[id][i]表示第id层的当前区间的第i个位置之前有多少个元素进入了左子树,当前区间的其余元素进入了右子树。如果你是初学的话,我知道这有点难懂,不过别急,继续。。。

4. 先来看一幅图:

就以这幅图进行详解:    //如果以下感觉看的很烦不妨直接用下面代码手推一遍上例,再回来看,会有惊喜的哟!

原数组可以当做第0层,第1层也行,看个人的代码习惯了。上面第三条提到有的元素进入了左子树,那以什么为划分标准呢?可以看到最上面是排序后的数组。原数组一分为二,左子树都是比4小的,于是划分标准:区间[l,r],比中间值sorted[(l+r)/2]小的都分到了左子树,其余的都在右子树。为什么要这样呢,其实可以对比归并排序和快排,实质是一样的。第0层[1,8],比sorted[4]=4小的都分在了左子树.你可能要问如果与中间值相等怎么办呢,这个下面再讨论。还记得t_l[0][i]吗,第0层的当前区间的第i个位置之前有多少个被分到了左子树(比sorted[mid]小--参照划分标准),t_l[0][1]=1,t_l[0][2]=1,t_l[0][3]=1,t_l[0][4]=2....我们要求区间第k大其实就是根据这个来求的。

上面讲到讲上层区间[l,r]一分为二,一半进入左子树,我们设置个lc=l,rc=mid+1,那么比sorted[mid]元素小的或等的肯定进入左子树,于是if(tree[id][i]<sorted[mid]) tree[id+1][lc++]=tree[id][i];if(tree[id][i]>sorted[mid]) tree[id+1][rc++]=tree[id][i]。看懂了吗,就是将上层区间小于等于中间值的一半为左子树,另一半为右子树,然后递归对这两个小的区间进行build(),类似归并排序,为什么是类似呢,因为这个过程是自上而下的,与归并排序相反。说到这里,可以这样理解:每层最多MAXN个元素,下一层就是这个区间按照划分标准分为两半分别为左右子树,然后递归下一层进行相同的操作,然后区间越分越小,左后达到叶子节点return
;这个过程并没有像线段树那样产生新的两个节点,而是相同大小的区间在每一层被越分越小。t_l[id][i]就储存了每个区间比sorted[mid]小的个数,我们在查询区间第k小的时候只需判断t[p].num[b]-t[p].num[a-1]>=k,如果满足说明进入左孩子的个数已经超过k个,那么就往左孩子里面查找同时更新查找区间。

    不知道你是不是更加稀里糊涂了,原谅我的表达能力有限,网上有很多博客,其实个人认为找几篇写得比较好的理解了思想然后模拟一下代码基本上就完全明白了--我的经历。

来看一下建树: //咳咳,划分树精髓,敲黑板划重点啦~!

先定义变量:

const int N=1e5+5;

int  tree[20][N], t_l[20][N];

int  a[N];//懒省事的我没有用sorted作为数组名。

void build(int l,int r,int id)
{
if(l==r) return ;
int mid=(l+r)/2;
int same=mid-l+1;
/* same用来标记和中间值val_mid相等的,且分到左孩子的数的个数
初始时,假定当前区间[lft,rht]有mid-lft+1个和valu_mid相等。
先踢掉比中间值小的,剩下的就是要插入到左边的 */
for(int i=l; i<=r; i++) if(tree[id][i]<a[mid]) same--;
int lc=l,rc=mid+1;
for(int i=l; i<=r; i++)
{
if(tree[id][i]<a[mid]) tree[id+1][lc++]=tree[id][i];
else if(tree[id][i]==a[mid]&&same) tree[id+1][lc++]=tree[id][i],same--;
else tree[id+1][rc++]=tree[id][i];
t_l[id][i]=t_l[id][l-1]+lc-l;
}
build(l,mid,id+1);
build(mid+1,r,id+1);
}

每个区间的初始值最好先全部赋为0。

如果你明白了上面的代码t_l[id][i]的含义也就理解的更加透彻了。

假设要查询区间[a,b]的第k小(网上很多博客都是说第k大,当我真正明白了这个划分树的时候其实查询的是第k小。后面会有两个例题讲解)如果tree[p].num[b]-tree[p].num[a-1]>=k,说明在左子树。如果和你想的不一样,或许你还没真正明白build()过程。t[p].num[b]-t[p].num[a-1]其实就是表示区间[a,b]之间有多少数进入了左子树(被分到了左边),按照划分标准被分到左边都是比中间值小的,如果大于等于k不就说明区间[a,b]内至少有k个比中间值小的,那么我们查询的就是第k小那么再递归下去查找即可。如果不满足呢,则在右子树嘛,只是这是的k要减去t[p].num[b]-t[p].num[a-1]再往右子树查找。

来看query()函数:

//[l,r]表示要查询的小区间,[L,R]表示大区间。
int query(int l,int r,int k,int L,int R,int id)
{
if(l==r) return tree[id][l];//到达了叶子节点
int mid=(L+R)/2;
int cnt=t_l[id][r]-t_l[id][l-1];
if(cnt>=k)
{
int newl=L+t_l[id][l-1]-t_l[id][L-1];//更新查询区间
int newr=newl+cnt-1;
return query(newl,newr,k,L,mid,id+1);
}
else
{
int newr=r+t_l[id][R]-t_l[id][r];
int newl=newr-(r-l-cnt);
return query(newl,newr,k-cnt,mid+1,R,id+1);
}
}

全篇我并没有花篇幅去细说query(),本菜其实理解了build()函数和query()函数查询第k小的大概,而区间更新那里我并没有去模拟这个过程,借用某大牛的话:其实你去模拟一下也就明白了。因为以后可能还会忘,时间原因我就没有太纠于细节(全靠模板.jgp)

以上如果有问题的话请联系我,或者有更好的想法也请联(教)系(教)我!

来看POJ-2104和HDU-2665。两道题代码几乎一致,划分树裸模板入门题:

n个数的序列,m次询问每次给出区间和查询大小k。

void build(int l,int r,int id)
{
if(l==r) return ;
int mid=(l+r)/2;
int same=mid-l+1;
for(int i=l; i<=r; i++) if(tree[id][i]<a[mid]) same--;
int lc=l,rc=mid+1;
for(int i=l; i<=r; i++)
{
if(tree[id][i]<a[mid]) tree[id+1][lc++]=tree[id][i];
else if(tree[id][i]==a[mid]&&same) tree[id+1][lc++]=tree[id][i],same--;
else tree[id+1][rc++]=tree[id][i];
t_l[id][i]=t_l[id][l-1]+lc-l;
}
build(l,mid,id+1);
build(mid+1,r,id+1);
}
int query(int l,int r,int k,int L,int R,int id)
{
if(l==r) return tree[id][l];
int mid=(L+R)/2;
int cnt=t_l[id][r]-t_l[id][l-1];
if(cnt>=k)
{
int newl=L+t_l[id][l-1]-t_l[id][L-1];
int newr=newl+cnt-1;
return query(newl,newr,k,L,mid,id+1);
}
else
{
int newr=r+t_l[id][R]-t_l[id][r];
int newl=newr-(r-l-cnt);
return query(newl,newr,k-cnt,mid+1,R,id+1);
}
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{ memset(t_l,0,sizeof(t_l));
for(int i=1; i<=n; i++)
{
scanf("%d",&tree[0][i]);
a[i]=tree[0][i];
}
sort(a+1,a+1+n);
build(1,n,0);
while(m--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",query(l,r,k,1,n,0));
}
} return 0;
}//代码精(A)简(C)才是王道!

昨晚突发奇想:如果会划分树还要学主席树吗?

狂搜问题求解答,在某吧里得到答案:划分树适用静态区间离线查询,主席树也能做,归并树也行。而如果涉及修改数列,则用树套树(主席树+树状数组),听起来好厉害的样子,可惜本弱还不会主席树!



初学划分树,小见解之!POJ-2104/HDU-2665的更多相关文章

  1. POJ 2104&HDU 2665 Kth number(主席树入门+离散化)

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 50247   Accepted: 17101 Ca ...

  2. POJ 2104 HDU 2665 主席树 解决区间第K大

    两道题都是区间第K大询问,数据规模基本相同. 解决这种问题, 可以采用平方划分(块状表)复杂度也可以接受,但是实际表现比主席树差得多. 这里大致讲一下我对主席树的理解. 首先,如果对于某个区间[L,R ...

  3. POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)

    题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设 ...

  4. [划分树] POJ 2104 K-th Number

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 51732   Accepted: 17722 Ca ...

  5. K-th Number POJ - 2104 划分树

    K-th Number You are working for Macrohard company in data structures department. After failing your ...

  6. poj 2104 K-th Number (划分树入门 或者 主席树入门)

    题意:给n个数,m次询问,每次询问L到R中第k小的数是哪个 算法1:划分树 #include<cstdio> #include<cstring> #include<alg ...

  7. poj 2104:K-th Number(划分树,经典题)

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 35653   Accepted: 11382 Ca ...

  8. poj 2104 K-th Number 划分树,主席树讲解

    K-th Number Input The first line of the input file contains n --- the size of the array, and m --- t ...

  9. POJ 2104 K-th Number(划分树)

    Description You are working for Macrohard company in data structures department. After failing your ...

随机推荐

  1. Java开发中存在这样的代码,反而影响整体整洁和可读性

    不完美的库类 不完美的库类(Incomplete Library Class) 当一个类库已经不能满足实际需要时,你就不得不改变这个库(如果这个库是只读的,那就没辙了). 问题原因 许多编程技术都建立 ...

  2. Ubuntu 14.04 安装深度音乐的方法(下载速度极慢未成功)

    原文地址:http://tieba.baidu.com/p/3023784357 由于14.04未提供gstreamer0.10-ffmpeg导致很多人安装深度音乐失败... 这里是安装方法: 1. ...

  3. leetcode128 Longest Consecutive Sequence

    思路: 维护一个unordered_map,key是每个连续区间的端点,value是该区间的长度. 实现: class Solution { public: int longestConsecutiv ...

  4. 对InitialContext的理解

        类InitialContext java.lang.Object   javax.naming.InitialContext 此类是执行命名操作的初始上下文. 所有命名操作都相对于某一上下文. ...

  5. 使用Kubernetes里的job计算圆周率后2000位

    使用Kubernetes里的job(作业),我们可以很方便地执行一些比较耗时的操作. 新建一个job.ymal文件: 定义了一个Kubernetes job,名称为pi,类型为job,容器名称为pi, ...

  6. Python学习日志9月17日 一周总结

    周一,9月11日 这天写的是过去一周的周总结,我从中找出当天的内容. 这天早晨给电脑折腾装机,早晨基本上没有学习,休息了一个早晨. 下午写的上周总结,完事做mooc爬虫课的作业,<Think P ...

  7. 屏蔽系统的Ctrl+c/x/v操作

    实现效果: 知识运用: KeyEventArgs类的Control, public bool Control {get;} //获取一个值 该值指示是否曾按下Ctrl键 KeyCode和Handled ...

  8. 两个input标签之间间隙问题的解决

    <input type="text"> <input type="button" value="搜索"> 代码显示效 ...

  9. MySQL 实时监控日志

    简单的梳理一下为什么要写这边文章,主要是学了ORM之后,发现通过ORM插入数据真的很方便,但是通过ORM生成的SQL语句又是怎么写的呢,百思不得姐.于是就找到了这个办法 首先查看一下查看MySQL 日 ...

  10. 给 MSYS2 添加国内源

    https://wiki.qt.io/MSYS2pacman -S base-devel git mercurial svn wget p7zip软件包 开发包 http://mirrors.ustc ...