初学划分树,小见解之!POJ-2104/HDU-2665
划分树
本来是学主席树的,可怜我等巨弱观群巨博客难解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的更多相关文章
- POJ 2104&HDU 2665 Kth number(主席树入门+离散化)
K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 50247 Accepted: 17101 Ca ...
- POJ 2104 HDU 2665 主席树 解决区间第K大
两道题都是区间第K大询问,数据规模基本相同. 解决这种问题, 可以采用平方划分(块状表)复杂度也可以接受,但是实际表现比主席树差得多. 这里大致讲一下我对主席树的理解. 首先,如果对于某个区间[L,R ...
- POJ 2104 K-th Number(区间第k大数)(平方切割,归并树,划分树)
题目链接: http://poj.org/problem? id=2104 解题思路: 由于查询的个数m非常大.朴素的求法无法在规定时间内求解. 因此应该选用合理的方式维护数据来做到高效地查询. 假设 ...
- [划分树] POJ 2104 K-th Number
K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 51732 Accepted: 17722 Ca ...
- K-th Number POJ - 2104 划分树
K-th Number You are working for Macrohard company in data structures department. After failing your ...
- poj 2104 K-th Number (划分树入门 或者 主席树入门)
题意:给n个数,m次询问,每次询问L到R中第k小的数是哪个 算法1:划分树 #include<cstdio> #include<cstring> #include<alg ...
- poj 2104:K-th Number(划分树,经典题)
K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 35653 Accepted: 11382 Ca ...
- 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 ...
- POJ 2104 K-th Number(划分树)
Description You are working for Macrohard company in data structures department. After failing your ...
随机推荐
- 流行的9个Java框架介绍: 优点、缺点等等
流行的9个Java框架介绍: 优点.缺点等等 在 2018年,Java仍然是世界上最流行的编程语言.它拥有一个巨大的生态系统,在全世界有超过900万Java开发人员.虽然Java不是最直接的语言,但是 ...
- StarUML安装与Win7不兼容解决
最近在学习建模工具(StarUML)发现 其他功能一切正常 但是无法显示代码导出功能, 正常界面如下: 我的安装确没有导出功能缺少C++,C# ,Java等导出功能 解决办法: 到StarUM ...
- BBS项目需求分析及表格创建
1.项目需求分析 1.登陆功能(基于ajax,图片验证码) 2.注册功能(基于ajax,基于forms验证) 3.博客首页 4.个人站点 5.文章详情 6.点赞,点踩 7.评论 --根评论 --子评论 ...
- hihocoder1821 取卡片游戏
思路: 博弈dp. 实现: #include <iostream> #include <algorithm> #include <cstring> using na ...
- [Luogu1343]地震逃生 最大流
题目链接:https://www.luogu.org/problem/show?pid=1343 dinic跑最大流. #include<cstdio> #include<cstri ...
- Windwos下安装和使用MongoDB
1)下载安装包下载路径:https://www.mongodb.com/download-center#community包名称:mongodb-win32-x86_64-3.4.10-signed. ...
- 电脑上文件的后缀名被隐藏,把一个文本文件改成.bat时,默认打开的还是文本。
1.打开文件夹,选择组织,点击“文件夹和搜索选项”,如图: 2.选择“查看”,找到“隐藏已知文件类型的扩展名”,不要勾选这一项,如图: 3.点击“确定”或者“应用”
- 题解 P1189 SEARCH
(传送门)[https://www.luogu.org/problemnew/show/P1189] 先反省一波:我以后再也不用getchar()+scanf了(日常爆零) 算是比较裸的搜索吧,在下用 ...
- nginx正则配置解释和fastadmin
参考:http://www.cnblogs.com/netsa/p/6383094.html 1 2 3 4 5 6 7 8 9 10 11 1.^: 匹配字符串的开始位置: 2. $:匹配字符串 ...
- 【Linux】启动Tomcat遇到Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
找不到JAVA_HOME路径,需要做以下变更: 找到启动路径所在的目录: cd bin/ vi catalina.sh 加入以下信息: export JAVA_HOME=/home/gongzi/ht ...