线段树的入门
来源:互联网 发布:江苏卫视网络在线直播 编辑:程序博客网 时间:2024/06/11 02:49
线段树的思想是什么呢?
直接从百度上偷一张图;
好,接下来介绍一下一棵可爱的线段树的组成和正确的打开姿势:
1.建树操作(build)
显然利用二叉树的性质,t 的两个儿子分别是 t * 2 和 t * 2 + 1
所以你的建树操作应该是不断的分一个点, 【L, R】 —— 【L, Mid】 和 【Mid + 1, R】;
当L == R的时候,就是你这个递归操作的边界,就等于这个点的初始值,然后就这么倒着算到树顶————
void build(long long t, long long be, long en){tree[t].data = 0, tree[t].need = 0;if(be == en){tree[t].data = num[be];return;}long long mid = (be + en) / 2;build(2 * t, be, mid);build(2 * t + 1, mid + 1, en);tree[t].data = tree[t * 2].data + tree[t * 2 + 1].data; }2.更新操作(up_date 绝对不是up_data 233!!!)
这个操作具体是怎么搞的呢?显然如果你把上面那张图看懂了的话,如果【L,R】有一个刚好有一个节点来维护他的话,直接在这个节点上操作就好了。
在上面那个基本思路下,就引出了线段树最漂亮的思想——延迟标记
你想想,很多时候只要不专门问你,你完全没有必要把每一个地方都算的清清楚楚,别人问到你了,你再算就是了。
这个标记是怎么用的呢?
首先要明白这个标记的具体含义:在我看了,t的标记的含义是指t的儿子们需要加的值,对,也就是说自己是不需要的!具体为什么,到了下面来讲。
所以也是和线段树一贯的操作,唯一的注意点就是到了边界条件的时候,你要手动把这个值直接加在这个点上。
接下来就是另一个关键点之一,因为你一定仔细思考后会发现一个问题,你的儿子们加了这个东西之后,你自己就不加了?这显然不科学的啊
所以就要重新算一遍这些倒霉的父亲的值,就有了一个很简单的操作——up
inline void up(long long t){tree[t].data = tree[t * 2 + 1].data + tree[t * 2].data;}好了,这个坑点已经填上了,那么整个up_date操作就有了个模型了
void up_date(long long t, long long be, long long en, long long b, long long e, long long add){if(b <= be && en <= e){tree[t].data += add * (en - be + 1);tree[t].need += add;return;}push_down(t, be, en);long long mid = (be + en) / 2;if(mid >= b)up_date(t * 2, be, mid, b, e, add);if(mid < e)up_date(t * 2 + 1, mid + 1, en, b, e, add);up(t);}3.下放标记操作(push_down)
这个地方我设计了一个小心机,后讲push_down。为什么呢?因为上方代码里的那句push_down我第一次就给忘了233,而且检查了好久,心里一万句***
push_down是什么意思呢?就是当你的儿子要被利用的时候,你就要把标记释放出去。当然,毕竟是懒癌晚期的咸鱼。你每次只下移一格,一旦他不需要了你绝不多走一步!
inline void push_down(long long t, long long be, long long en){if(tree[t].need == 0)return;tree[t * 2].need += tree[t].need;tree[t * 2 + 1].need += tree[t].need;long long mid = (be + en) / 2;tree[t * 2].data += (mid - be + 1) * tree[t].need;tree[t * 2 + 1].data += (en - mid) * tree[t].need;tree[t].need = 0;}4.查询操作(query)
查询操作就是把你的【L, R】不断的分为一个个小的区间,然后把答案加起来
特别简单~
所以来强调一点点地方(同时也是填前面的坑233)
为什么前面的标记专门要把自己的值先更新一波,原因很简单
你想,当你到了你的递归边界的时候,你显然要把这个值加进去,好,那么你这个点的标记怎么办?临时加进去?的确是一种办法,但是显然不那么科学啊~
能简则简嘛~
long long query(long long t, long long be, long long en, long long b, long long e){long long ret = 0;if(b <= be && en <= e){return tree[t].data;}push_down(t, be, en);long long mid = (be + en) / 2;if(b <= mid) ret += query(t * 2, be, mid, b, e);if(mid < e)ret += query(t * 2 + 1, mid + 1, en, b, e);return ret;}
综上所述:
完整题目加代码如下:
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出包含若干行整数,即为所有操作2的结果。
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据已经过加强^_^,保证在int64/long long数据范围内)
输入输出样例
5 51 5 4 2 32 2 41 2 3 22 3 41 1 5 12 1 4
11
8
20
#include<cstdio>using namespace std;struct lpl{long long need;long long data;}tree[400005];long long n, m, mark, x, y, k;long long num[100005];void build(long long t, long long be, long en){tree[t].data = 0, tree[t].need = 0;if(be == en){tree[t].data = num[be];return;}long long mid = (be + en) / 2;build(2 * t, be, mid);build(2 * t + 1, mid + 1, en);tree[t].data = tree[t * 2].data + tree[t * 2 + 1].data; }inline void up(long long t){tree[t].data = tree[t * 2 + 1].data + tree[t * 2].data;}inline void push_down(long long t, long long be, long long en){if(tree[t].need == 0)return;tree[t * 2].need += tree[t].need;tree[t * 2 + 1].need += tree[t].need;long long mid = (be + en) / 2;tree[t * 2].data += (mid - be + 1) * tree[t].need;tree[t * 2 + 1].data += (en - mid) * tree[t].need;tree[t].need = 0;}void up_date(long long t, long long be, long long en, long long b, long long e, long long add){if(b <= be && en <= e){tree[t].data += add * (en - be + 1);tree[t].need += add;return;}push_down(t, be, en);long long mid = (be + en) / 2;if(mid >= b)up_date(t * 2, be, mid, b, e, add);if(mid < e)up_date(t * 2 + 1, mid + 1, en, b, e, add);up(t);}long long query(long long t, long long be, long long en, long long b, long long e){long long ret = 0;if(b <= be && en <= e){return tree[t].data;}push_down(t, be, en);long long mid = (be + en) / 2;if(b <= mid) ret += query(t * 2, be, mid, b, e);if(mid < e)ret += query(t * 2 + 1, mid + 1, en, b, e);return ret;}int main(){scanf("%lld%lld", &n, &m);for(int i = 1; i <= n; ++i)scanf("%lld", &num[i]);build(1, 1, n);for(int i = 1; i <= m; ++i){scanf("%lld", &mark);if(mark == 1){scanf("%lld%lld%lld", &x, &y, &k);up_date(1, 1, n, x, y, k);}else{scanf("%lld%lld", &x, &y);printf("%lld\n", query(1, 1, n, x, y));}} return 0;}
线段树( ^_^ )/~~拜拜
- 线段树的入门
- 线段树的入门级
- 线段树的入门级
- 线段树的入门小结
- 【线段树】线段树入门
- [线段树]线段树 入门
- hdu 1754 线段树入门的题
- 【线段树】new出来的入门
- 线段树的入门级 总结
- 线段树的入门级 总结
- 蒟蒻的入门之线段树
- 线段树的入门级 总结
- 【线段树】线段树入门之入门
- 【线段树】线段树入门之入门
- 【线段树】线段树入门之入门
- 【线段树】线段树入门之入门
- 高级数据结构 | 线段树的入门与入门
- 线段树入门之入门
- 浅析C++函数指针和函数对象
- jsp生成json数据
- 由0到4五个数字,组成5位数,每个数字用一次,但十位和百位不能为3(当然万位不能为0),输出所有可能的五位数。
- 教你如何使用Gerrit
- Android application的label 和Activity的label 的区别
- 线段树的入门
- jQuery获取Select选择的Text(文本信息)和 Value属性的值,select语法解释;单选框和复选框
- 配置Tomcat使用HTTPS协议
- openstack【Kilo】入门 【keystone篇】八:验证keystone安装部署
- 算法与设计——枚举——ch_01
- DAY3-2017-11-17
- poj3320 尺取法
- swiper.js插件实现图片滚动效果
- Windows10下的docker安装与入门 (一)使用docker toolbox安装docker