初学划分树,小见解之!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 ...
随机推荐
- ubuntu快捷键收集
Ctrl+Alt+T 终端 -Ctrl+Shift+C 终端复制 -Ctrl+Shift+V 终端粘贴 -Ctrl+L 清屏 -Ctrl+; 从剪切板中获得输入提示(不小心点到被诡异的提示吓到了) - ...
- 访问者模式和php实现
访问者模式: 表示作用于某个对象结构中的各个元素的操作.它使你可以在不改变各个元素类的前提下定义作用于这些元素的操作. 角色: 1)抽象访问者:为该对象结构中具体元素角色声明一个访问操作接口.该操作接 ...
- 跨平台C++开源代码的两种常用编译方式
作者:朱金灿 来源:http://blog.csdn.net/clever101 跨平台C++开源代码为适应各种编译器的编译,采用了两种方式方面来适配.一种是makefile方式.以著名的空间数据格式 ...
- VC运行时库(/MD、/MT等)
VC项目属性→配置属性→C/C++→代码生成→运行时库 可以采用的方式有:多线程(/MT).多线程调试(/MTd).多线程DLL(/MD).多线程调试DLL(/MDd).单线程(/ML).单线程调试( ...
- list control 【转】
1. CListCtrl 风格 LVS_ICON: 为每个item显示大图标 LVS_SMALLICON: 为每个item显示小图标 LVS_LIST: 显示一列带有小图标的ite ...
- iOS组件化开发· 什么是组件化
越来越多公司,开始了组件化,你还要等到什么时候...... 说到开发模式,我们最熟知的开发模式 MVC 或者最近比较热门的MVVM.但是我今天说的组件化的开发,其实MVC不是一类的.它其实是····· ...
- SQL,数据库连接
- Luogu P4609 [FJOI2016]建筑师&&CF 960G Bandit Blues
考虑转化题意,我们发现其实就是找一个长度为\(n\)的全排列,使得这个排列有\(A\)个前缀最大值,\(B\)个后缀最大值,求方案数 我们考虑把最大值拎出来单独考虑,同时定义一些数的顺序排列为单调块( ...
- MIPS简单入门
What ‘s the MIPS? 汇编语言 汇编语言是一类语言的总称,因处理器不同,而对应的不同机器的指令集也不同,产生了很多种汇编语言. 目前最流行的是ARM,MIPS,x86.ARM用于大量的移 ...
- sql server 处理分母为空
SP 前面加下面设置,会忽略错误结果 直接返回null 不会导致SP 失败 SET ANSI_WARNINGS OFFSET ARITHABORT OFFSET ARITHIGNORE ON