线段树(Segment Tree)
node
入门模板题 洛谷oj P3372函数
题目描述ui
如题,已知一个数列,你须要进行下面两种操做:spa
1.将某区间每个数加上x指针
2.求出某区间每个数的和code
输入格式blog
第一行包含两个整数N、M,分别表示该数列数字的个数和操做的总个数。递归
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。get
接下来M行每行包含3或4个整数,表示一个操做,具体以下:it
操做1: 格式:1 x y k 含义:将区间[x,y]内每一个数加上k
操做2: 格式:2 x y 含义:输出区间[x,y]内每一个数的和
输出格式
输出包含若干行整数,即为全部操做2的结果。
输入样例
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例
11
8
20
数据范围
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
才学会写线段树不久……随便扯一篇笔记orz
0x00 线段树概念
线段树是一种二叉搜索树。它将一个区间划分红一些单元区间,每一个单元区间对应线段树中的一个叶结点。
插入(删除)操做的时间复杂度为O(logn)。
0x01 树形结构及建树
做为一棵树,线段树的结构体大概是这样的:
struct SegmentTree{ //树的结构体 long long l,r; //覆盖的区间的左右指针 long long value; //该节点上维护的值 long long tag; //lazy标记,下面会写到,这也是线段树的精髓 }node[MAX_N*4+5];
要想建一棵线段树,能够利用线段树是个二叉树的性质,进行递归建树:
void build(REG long long p,REG long long l,REG long long r){ //递归建树 node[p].l=l,node[p].r=r; if (l==r){ //访问到了最底部,不可再分 node[p].value=read(); return ; } long long mid=(l+r)>>1; build(p<<1,l,mid); build((p<<1)+1,mid+1,r); node[p].value=node[p<<1].value+node[(p<<1)+1].value; }
这样咱们就拥有了一棵树。
0x02 lazy标记(懒标记)
上面提到过,线段树的精髓是lazy标记。无论是要查询仍是更改数列,都绕不开它。
若是要修改一段区间(好比同时都增长k),最容易想到的方法是暴力枚举,一个个修改。这样操做m次,最坏时间复杂度是O(mn)。显然是过不去的。
因此咱们能够想:若是不修改那么多节点呢?很容易想到,咱们只关心查询须要用到的节点——并非每个被修改的节点都会在查询中被访问到的。打个比方:假设只有一次修改,一次询问。修改修改[1,16],而询问只关心[1,8],那么咱们对于[9,16]的修改都是没有意义的。
能够想到,若是点i的两个儿子都要同时增长k,考虑先不修改两个儿子,而给点i打一个标记,标记一下i的两个儿子都要被修改。
若是在下面的询问里要访问到i的两个儿子之一,咱们再对i的两个儿子进行修改(或下传标记),再把标记归零。若是两个儿子都会被访问到,就直接取i的值(i点维护的value值是两个儿子节点的和)。
这差很少就是lazy标记的思想,时间复杂度就降到了O(mlogn)。
写一个下传标记的函数:
void push_down(REG const long long& p){ node[p<<1].value+=(node[p].tag*(node[p<<1].r-node[p<<1].l+1)); node[(p<<1)+1].value+=(node[p].tag*(node[(p<<1)+1].r-node[(p<<1)+1].l+1)); node[p<<1].tag+=node[p].tag; node[(p<<1)+1].tag+=node[p].tag; node[p].tag=0; }
按照这样的思想,咱们就能够写出线段树了。其余的在最终代码里写了,再也不赘述。
0x03 AC代码
#include <cstdio> #define REG register #define MAX_N 100000 using namespace std; long long n,m; long long a[MAX_N+5]; long long ch,x,y,z; inline long long read(){ //快速读入 REG long long ch=getchar(),x=0,f=1; while (ch<'0'||ch>'9'){ if (ch=='-') f=-1; ch=getchar(); } while (ch<='9'&&ch>='0'){ x=x*10+ch-'0'; ch=getchar(); }return x*f; } struct SegmentTree{ //树的结构体 long long l,r; long long value; long long tag; }node[MAX_N*4+5]; void build(REG long long p,REG long long l,REG long long r){ //递归建树 node[p].l=l,node[p].r=r; if (l==r){ //访问到了最底部,不可再分 node[p].value=read(); return ; } long long mid=(l+r)>>1; build(p<<1,l,mid); build((p<<1)+1,mid+1,r); node[p].value=node[p<<1].value+node[(p<<1)+1].value; } void push_down(REG const long long& p){ node[p<<1].value+=(node[p].tag*(node[p<<1].r-node[p<<1].l+1)); node[(p<<1)+1].value+=(node[p].tag*(node[(p<<1)+1].r-node[(p<<1)+1].l+1)); node[p<<1].tag+=node[p].tag; node[(p<<1)+1].tag+=node[p].tag; node[p].tag=0; //标记归零 } void change(REG long long p,REG const long long& x,REG const long long& y,REG long long& z){ if (x<=node[p].l&&y>=node[p].r){ //是否彻底在区间内 node[p].value+=(z*(node[p].r-node[p].l+1)); node[p].tag+=z; return ; } if (node[p].tag) push_down(p); //不彻底在区间内,若是有lazy标记,下传 REG long long mid=(node[p].l+node[p].r)>>1; if (x<=mid) change(p<<1,x,y,z); if (y>mid) change((p<<1)+1,x,y,z); node[p].value=node[p<<1].value+node[(p<<1)+1].value; } inline long long ask(REG long long p,REG const long long& x,REG const long long& y){ if (x<=node[p].l&&y>=node[p].r) return node[p].value; push_down(p); REG long long mid=(node[p].l+node[p].r)>>1; long long ans=0; if (x<=mid) ans+=ask(p<<1,x,y); if (y>mid) ans+=ask((p<<1)+1,x,y); return ans; } int main(){ // freopen("test1.txt","r",stdin); n=read(),m=read(); build(1,1,n); REG long long ch; while (m--){ ch=read(); if (ch==1){ x=read(),y=read(),z=read(); change(1,x,y,z); } else{ x=read(),y=read(); printf("%lld\n",ask(1,x,y)); } } return 0; }
若是写的有哪里不对,欢迎指出,轻喷orz
[参考百度百科及网友讲解]