【模板】线段树 1

题目描述

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

  1. 将某区间每一个数加上 kk。
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n, mn,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。

接下来 mm 行每行包含 33 或 44 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [x, y][x,y] 内每个数加上 kk。
  2. 2 x y:输出区间 [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

说明/提示

对于 30\%30% 的数据:n \le 8n≤8,m \le 10m≤10。
对于 70\%70% 的数据:n \le {10}^3n≤103,m \le {10}^4m≤104。
对于 100\%100% 的数据:1 \le n, m \le {10}^51≤n,m≤105。

保证任意时刻数列中所有元素的绝对值之和 \le {10}^{18}≤1018。

【样例解释】

 

//洛谷3372,线段树,区间修改+区间查询
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e5 + 10;
ll a[N];		//记录数列的元素,从a[1]开始 
ll tree[N << 2];		//tree[i]为第i个节点的值,表示一个线段区间的值,如最值、区间和 
ll tag[N << 2];			//tree[i]为第i个节点的lazy-Tag,统一记录这个区间的修改 
ll ls(ll p){
	return p << 1;		//定位做儿子:p*2 
} 
ll rs(ll p){
	return p << 1 | 1;		//定位右儿子:p*2+1 
}
void push_up(ll p){			//从下向上传递区间值 
	tree[p] = tree[ls(p)] + tree[rs(p)];
	//本题是求区间和,如果是求最小值,改为tree[p] =  min(tree[ls(p)],tree[rs(p)]);
}
void build(ll p,ll pl,ll pr){		//建树。p为节点编号,它指向区间[pl,pr]
	tag[p] = 0;						//lazy-Tag标记 
	if(pl == pr){					//最底层的叶子,赋值 
		tree[p] = a[pl];
		return ;
	}
	ll mid = (pl + pr) >> 1;		//分治:折半 
	build(ls(p),pl,mid);			//左儿子 
	build(rs(p),mid+1,pr);			//右儿子 
	push_up(p);						//从下往上传递区间值 
}
void addtag(ll p,ll pl,ll pr,ll d){	//给节点p打tag标记,并更新tree 
	tag[p] += d;					//打上tag标记 
	tree[p] += d * (pr - pl + 1);	//计算新的tree 
}
void push_down(ll p,ll pl,ll pr){	//不能覆盖时,把tag传给子树 
	if(tag[p]){						//有tag标记,这是以前做区间修改时留下的 
		ll mid = (pl + pr)>> 1;		 
		addtag(ls(p),pl,mid,tag[p]);//把tag标记传给左子树 
		addtag(rs(p),mid+1,pr,tag[p]);//把tag标记传给右子树 
		tag[p] = 0;					//p自己的tag被传走了,归零 
	}         
}
void update(ll L,ll R,ll p,ll pl,ll pr,ll d){//区间修改:[L,R]内每个元素加d 
	if(L <= pl&&pr <= R){			//完全覆盖,直接返回这个节点,它的子树不用再深入了 
		addtag(p,pl,pr,d);			//给节点p打tag标记,下一次区间修改到p时会用到 
		return;
	}
	push_down(p,pl,pr);				//如果不能覆盖,把tag传给子树 
	ll mid = (pl + pr) >> 1;
	if(L <= mid) update(L,R,ls(p),pl,mid,d);	//递归左子树 
	if(R > mid) update(L,R,rs(p),mid+1,pr,d);	//递归右子树 
	push_up(p);									//更新 
}
ll query(ll L,ll R,ll p,ll pl,ll pr){
	//查询区间[L,R],p是当前节点(线段)的编号,[pl,pr]是节点p表示的线段区间 
	if(pl >= L&&R >= pr) return tree[p];		//完全覆盖,直接返回 
	push_down(p,pl,pr);							//不能覆盖,递归子树 
	ll res = 0;
	ll mid = (pl + pr) >> 1;
	if(L <= mid) res += query(L,R,ls(p),pl,mid);//左子节点有重叠 
	if(R > mid) res += query(L,R,rs(p),mid+1,pr);//右子节点有重叠 
	return res;
}
int main(){
	ll n,m;
	scanf("%lld%lld",&n,&m);
	for(ll i = 1;i <= n;i++) scanf("%lld",&a[i]);
	build(1,1,n);								//建树 
	while(m--){
		ll q,L,R,d;
		scanf("%lld",&q);
		if(q == 1){								//区间修改:[L,R]的每个元素加d 
			scanf("%lld%lld%lld",&L,&R,&d);
			update(L,R,1,1,n,d);
		}
		else{									//区间查询:[L,R]区间和 
			scanf("%lld%lld",&L,&R);
			printf("%lld\n",query(L,R,1,1,n));
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值