线段树的入门

来源:互联网 发布:江苏卫视网络在线直播 编辑:程序博客网 时间: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;}


线段树( ^_^ )/~~拜拜