划分树

本来是学主席树的,可怜我等巨弱观群巨博客难解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. ps 进程管理

    一. 进程管理 1. pstree 2. ps 3. top 4. nice 5. free 6. screen 二. 程序与进程 程序是静态的文件,进程是动态运行的程序. 三. 进程和线程 一个程序 ...

  2. Android中的ListView属性使用总结

    Android中使用ListView控件比较常见,如果能知道常用的一些属性使用,肯定会少很多坑. 1.ListView是常用的显示控件,默认背景是和系统窗口一样的透明色,如果给ListView加上背景 ...

  3. xml文件解析和序列化

    转载:http://blog.csdn.net/liuhe688/article/details/6415593 XmlPullParser parser = Xml.newPullParser(); ...

  4. LookAround开元之旅(持续更新中...)

    应用介绍随便瞧瞧是一款为android用户量身定做的免费图文资讯软件集美食,文学,语录等频道于一体界面简洁,操作流畅,图文分享,个性收藏是广大卓粉的必备神器APK下载 -->https://ra ...

  5. rsyslog+analyzer

    环境:最小化centos6.2 准备:rsyslog-4.6.1.tar.gz  loganalyzer-3.6.3.tar.gz wget http://download.adiscon.com/l ...

  6. OPENFIRE 使用Hazelcast插件进行集群

    参考资料:http://www.linuxidc.com/Linux/2014-01/94850.htm   https://www.igniterealtime.org/projects/openf ...

  7. map最基本操作

    #include<iostream> #include<map> using namespace std; int main() { /*map<int,char> ...

  8. Scrapy-架构

    Scrapy架构(各组件的功能)及Scrapy引擎控制数据流的过程 1. Scrapy架构图(绿线是数据流向): □ Scrapy引擎(Engine):引擎负责控制数据流在系统的所有组件中流动,并在相 ...

  9. MAC 安装汇编编译工具 NASM

    直接运行nasm报错: 开始安装: brew reinstall nasm

  10. javascript的offset、client、scroll使用方法

    offsetTop 指元素距离上方或上层控件的位置,整型,单位像素. offsetLeft 指元素距离左方或上层控件的位置,整型,单位像素. offsetWidth 指元素控件自身的宽度,整型,单位像 ...