线段树-基础

来源:互联网 发布:揭秘淘宝刷到单流程图 编辑:程序博客网 时间:2024/05/17 05:17
线段树是一种高级数据结构,可以用于解决动态的区间最值问题和区间求和问题。这一种算法比纯粹的模拟算法要快。例如每次修改单个数,求一个区间的最优值。用模拟算法每一次修改单个数要O(1)的时间,查询要O(N)的时间,总共修改M个数,时间复杂度为O(NM),而线段树每一次修改需要O(logN)的时间,修改M次,总时间为O(MlogN),整体会比模拟算法快得多。
二叉树通常是一棵完全二叉树,占2^N的空间,如果不是刚好2^N,就会稍微浪费一点空间。根据名字,可以知道,它储存的是一段数据,如下图表示。
 线段树 - 赵子睿 - 赵子睿的博客
其中的(1,8)表示的是从第一个值到第八个值的区间,这一段就由(1,8)来保存,下面的以此类推。最下层只保存单个数字,但也表示了一个只有一个值的区间。
这样的树不断将区间二分,每一个节点都可以直接管理自己的左右儿子,左儿子管理这个区间的前半部分,右儿子管理这个区间的后半部分。
这样的一颗线段树可以近似地看做是一棵满二叉树。同样,它的深度不会超过线段树-基础 - 赵子睿 - 赵子睿的博客
管理每一个节点,可以用一个结构体数组来保存,每一个节点都记录自己的左端点和右端点,左儿子和右儿子,以及该区间的最优值。
创建一个节点,可以用一个指针指向这一个数组的空的节点,然后把要替代的值都放进去。
要建立一颗二叉树,可以运用递归的过程实现,首先建立根节点,检查左端点和右端点,发现左端点在右端点的前面,证明这一个区间可以一分为二,再建立它的左子树和右子树,直到发现左端点和右端点相同,只剩一个值,就不用在二分了。
具体程序如下:

int getPoint(int l, int r, int max)
{
fp++;//新建一个空节点
f[fp].l = l;
f[fp].r = r;
f[fp].max = max;
return fp;//返回该节点的地址
}

int create(int l, int r)
{
int now = getPoint(l,r,-oo);//创建节点,记录节点地址
if (l<r)
{
f[now].lc = create(l, (l+r)/2);//建立左子树
f[now].rc = create((l+r)/2+1, r);//建立右子树
}
return now;
}

那么,有了创造,自然也要有修改。修改分为两种,区间修改和单个节点修改。单个节点修改很简单,从根节点开始递归,往下面扫描,发现要修改的节点,就修改它,并向上更新树的最优值(和),最后回到根节点。

代码如下:

void update(int root , int p, int x)
{
int l = f[root].l;
int r = f[root].r;
if ( p<l || r<p ) return ;//如果要修改的节点不在此区间内,停止递归,避免过多操作

if (l==r) { f[root].max=x; return ; }//如果这个区间刚好是要修改的叶节点,就修改它

update(f[root].lc, p, x);
update(f[root].rc, p, x);

f[root].max = max ( f [ f[root].lc ].max, f [ f[root].rc ].max );//更新当前节点
}

单个节点修改很简单,但是还有区间修改,一次更改整个区间,如果一个一个数进行单个修改,速度会比模拟还要慢,所以,这里就有一种更好的办法,使用lazy-tag的思想,就是给每一个要修改的节点标记一下,记录需要增加的数值,而不需要现在就把这个值加上去。当调用到这一个节点时,发现被标记过,就把增量加进去,并且给子节点也增加需要增加的数。

这里对于部分人来说有一个问题:修改某个区间,最优值就直接加上了增量。事实上,修改这一个区间,区间中每个数都相加同样的值,最大值依然不变,只比原来多了增量,就算增量是负数,也同样是这样。

事实上,更新单个节点也是区间更新中的一个特例,用区间更新同样能够操作,只不过左端点等于右端点。

代码如下:

void push(int root)//将当前节点的增量压入当前节点中
{
if (!f[root].s) return;
if (f[root].lc != 0)//增量传递给左儿子
{
f[f[root].lc].s = true;
f[f[root].lc].delta += f[root].delta;
}
if (f[root].rc != 0)//增量传递给右儿子
{
f[f[root].rc].s = true;
f[f[root].rc].delta += f[root].delta;
}
if (f[root].rc == 0 && f[root].lc == 0)
{
f[root].max += f[root].delta;
}
f[root].s = false;
f[root].delta = 0;//增加完当前节点,增量清空
}

void update(int root, int l, int r, int d)
{
push(root);
if (l==f[root].l && r==f[root].r)//区间刚好匹配
{
f[root].s = true;
f[root].delta += d;
return;//标记增量
}
int mid = (f[root].l+f[root].r) / 2;
if (l <= mid) update(f[root].lc,l,min(mid,r),d);//更新左子树
if (r >= mid+1) update(f[root].rc,max(mid+1,l),r,d);//更新右子树
}

有了创造,有了修改,可是没有查询,这一棵线段树也发挥不了多大作用。查询线段树,用到的一样是很巧妙的递归操作。查询一个区间的最优值,从根节点开始,向下递归,如果是查询的区间被一分为二,就查询左右儿子的区间,返回查询到的答案,合并两个答案,得到最优值。如果在左(右)区间,就直接递归向下找。最后返回的结果就是查询的区间的最优值。

 代码如下:

int query(int root,int l,int r)
{
if (l==f[root].l && r==f[root].r) return f[root].max;//区间完全匹配,返回当前区间最优值

int mid = (f[root].l+f[root].r) / 2;
int ans = -oo;
if (l <= mid) ans = max(ans,query(f[root].lc,l,min(mid,r)));//查找左儿子区间
if (r >= mid+1) ans = max(ans,query(f[root].rc,max(mid+1,l),r));//查找右儿子区间

return ans;//返回最优值
}

注意,如果是区间修改需要在一开始将增量增加到节点中。即push(root);


//以上区间修改为不正确的……尚未修改完毕……
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宝宝不会喝奶粉怎么办 两个月宝宝不长肉怎么办 打疫苗后发烧怎么办 孕期不爱吃水果怎么办 孕期很少吃水果怎么办 三个月小孩不吃奶粉怎么办 三个月宝宝偏瘦怎么办 破壁机打果汁有沫怎么办 宝宝7个月坐不稳怎么办 婴儿头睡偏了怎么办天 宝宝不爱趴着怎么办 宝宝喜欢竖着抱怎么办 婴儿抱习惯了怎么办 新生儿总让抱着放下就哭可怎么办 三个月宝宝认人怎么办 三个月的宝宝认生怎么办 一岁半宝宝尿黄怎么办 一岁多宝宝尿少怎么办 1岁宝宝一晚没尿怎么办 抗利尿激素少怎么办 小孩夜里尿多怎么办 一岁宝宝认生怎么办 婴儿一个月认生怎么办 婴儿大便带血丝怎么办 两个月宝宝认生怎么办 晚上宝宝认人怎么办 小孩长白头发怎么办 三个月婴儿脚力不足怎么办 未满月宝宝便秘怎么办 婴儿5天没拉大便怎么办 儿童三天没大便怎么办 婴儿4天没拉大便了怎么办 孩子不天天排便怎么办 宝宝便秘5天怎么办 小孩子3天便秘怎么办 宝宝4天没拉大便怎么办 儿童不拉大便怎么办 吃母乳没有大便怎么办 婴儿老是抓头皮怎么办 婴儿两天不大便怎么办 不排便怎么办小窍门