线段树模板详解

来源:互联网 发布:诺基亚n8刷windows 编辑:程序博客网 时间:2024/06/15 18:54

原题:洛谷:https://www.luogu.org/problem/show?pid=3372
题目描述

如题,已知一个数列,你需要进行下面两种操作:

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的结果。

输入输出样例

输入样例#1:
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1:
11
8
20
说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=8,M<=10

对于70%的数据:N<=1000,M<=10000

对于100%的数据:N<=100000,M<=100000

(数据已经过加强^_^,保证在int64/long long数据范围内
代码解析已经在代码中呈现!!!

//线段树,初学者适用 //与二叉堆性质相似a的左儿子a[2*i]右儿子a[2*i+1],线//段树是将每个区间[l,r]分解成[l,m],[m+1,r],m=(l+r)/2//直到l==r #include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define maxn 1000000using namespace std;int x,y,k;long long add[maxn]/*表示每个点的标记(惰性标记)*/;long long a[maxn];//存储最初的数据 struct node{    int left,right;    long long sum;//记录该条线段出现的次数 }tree[maxn];//保存每个点左右端点和极值//数组模拟,空间开到4倍防止访问越界 void buildtree(int i,int l,int r){//构造线段树     tree[i].left=l;    tree[i].right=r;    if(l==r){//找到叶子节点并且赋值         tree[i].sum=a[l];        return;    }//如果该点是叶节点     int mid=(l+r)>>1;    buildtree(i<<1,l,mid);//左移一位(二进制) //左子树     buildtree((i<<1)|1,mid+1,r);//右子树 //按位或eg:3|2=0011|0010=0011    tree[i].sum=tree[i<<1].sum+tree[(i<<1)+1].sum;//回溯维护区间和 }void pushdown(int i){//下放惰性标记     int lc=i<<1;;    int rc=(i<<1)+1;    tree[lc].sum+=(tree[lc].right-tree[lc].left+1)*add[i];    tree[rc].sum+=(tree[rc].right-tree[rc].left+1)*add[i];    add[lc]+=add[i];    add[rc]+=add[i];    add[i]=0;//此处下放标记 }void update(int i,int x,int y,long long k){//区间修改     int lc=i<<1;    int rc=(i<<1)|1;    if(tree[i].left>y || tree[i].right<x){        return;//如果此区间完全无关     }    if(x<=tree[i].left && tree[i].right<=y){        tree[i].sum+=(tree[i].right-tree[i].left+1)*k;//加至此处就不继续往下加         add[i]+=k;//存放惰性标记     } //如果此处是完全有关区间    else{        if(add[i]){            pushdown(i);//下放惰性标记         }        update(lc,x,y,k);        update(rc,x,y,k);        tree[i].sum=tree[lc].sum+tree[rc].sum;    }//二分添加 } long long find(int i,int x,int y){    int lc=i<<1;    int rc=(i<<1)+1;    if(x<=tree[i].left && tree[i].right<=y){        return tree[i].sum;    }//当前节点的区间完全被目标区间包含     if(tree[i].left>y || tree[i].right<x){        return 0;    }//完全在左子树中or右子树     if(add[i]){        pushdown(i);//下放惰性标记     }     return find(lc,x,y)+find(rc,x,y);//目标区间左右都有分布 }//重点详解: //void pushdown(int);//下放惰性标记 //此处标记指的是惰性标记,实际上是让子节点暂时处于//不更新状态等到用到的时候在做更新而区间加时要把和//那个区间有关的区间全部价值,不然不配合输出//超时了就要优化。//你想啊,虽然x~y区间要增加一个值,难道这个区间就一//定要用吗?//如果区间值增加了但后来没有询问,我们一开始为什么//要增加呢?//正如背古文,如果考试不考,我们为什么要背呢?//所以lazy思想就怎么产生了。//lazy,就是懒嘛,就是先不被古文,等到考试要考了再//去背嘛;//先不增加区间,等到询问时在去增加嘛;//我们可以搞一个add数组,专门把编号为num的区间要加//的值记录下来//如果要用了,再给线段树num加上v[num]的值,再把//add[num]//传给v[num*2]和v[num*2+1];然后v[num]清零//“如果要用了”这一步用一个clean函数实现//void buildtree(int,int,int);//构造线段树//void update(int,int,int,long long);//区间修改//1,起始区间,终止区间,修改值 //long long find(int,int,int);//区间查询 //1,区间起始值,区间终止值 int main(){    int n,m,flag;    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++){        scanf("%lld",&a[i]);    }    buildtree(1,1,n);//构造线段树    for(int i=0;i<m;i++){        scanf("%d",&flag);        if(flag==1){            scanf("%d%d%lld",&x,&y,&k);            update(1,x,y,k);        }        if(flag==2){            scanf("%d%d",&x,&y);            printf("%lld\n",find(1,x,y));        }     }     return 0;   } //假设线段树处理的最大长度是n,那么总结点数不超过2*n//线段树分解数量级:线段树能把任意一条长度为M的线段分为//不超过2log(M)条线段,很大的数一下子变小,使得线段树查//询与修改复杂度都在O(log2(n))范围内能得以解决//线段树是一棵二叉树,深度约为log2n左右//所以线段树空间消耗O(n),由于他的深度性质,解决问题效率//更高 
原创粉丝点击