线段树模板详解

原题:洛谷: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 1000000
using 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),由于他的深度性质,解决问题效率
//更高 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值