洛谷 P3372 线段树 1

今天植树节,来种一棵线段树。

传送门


题目描述

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

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

输入输出样例

输入样例:

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

说明

时空限制:1000 ms , 128 M

数据规模:

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

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

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

样例说明:

洛谷 P3372 线段树 1_第1张图片


题解

一般来说,拿到这道题的第一个反应应该是硬模,然后一看数据,发现撑不过 O(100000*100000)。‘

于是,我们就有了线段树这种神奇的东西。至于线段树呢,就是下面这种神奇的东西 ↓

洛谷 P3372 线段树 1_第2张图片

对于线段树中的每一个子节点而言,他们都储存着一段子序列,而对于每一个叶子节点而言,他们储存的是单个的信息元素,而每一个父节点都是他两个子节点的整合。

本题是线段树改段求段的模板题,也就是区间修改与区间查询。线段树还有点修改等其他功能,欲知推荐推荐一篇博文:

线段树详解 (原理,实现与应用)


、建树与

1.建树

从根开始,往下建树。根的掌管范围为1~n ( l=1,r=n )。当一个节点掌管着不止一个信息元素时,便让他的左儿子掌管1~mid (mid=(l+r)/2),右儿子掌管 mid+1~r 。当 l==r (叶子节点)时,则直接赋值。

2.维护

若一个节点有左右儿子,则它的值等于它左儿子的值加上它右儿子的值。

建树代码 

void bt(int l,int r)
{
	len++;int now=len;
	tr[now].l=l;tr[now].r=r;tr[now].lc=tr[now].rc=-1;tr[now].c=tr[now].lz=0;//l,r为左右边界,lc为左儿子,rc为右儿子,c为值,lz为lazy tag
	if(l


、区间修改与查询

1.修改

在进行区间修改的时候,我们引入一个概念:lazy tag (懒标记,简称 lazy)。它的作用是向下延迟修改,但因已修改自身的值,在向上传递信息时返回的结果是正确的。

if(tr[now].l==l && tr[now].r==r)
{
	tr[now].c+=(r-l+1)*k;
	tr[now].lz+=k;	
	return;
}  


由于我们这样的记录方式,在我们每找到一棵子数就需向下推一次 lazy(pushdown 操作)。推的时候我们先修改lazy的值,再分别修改左右儿子的值(倒过来也没有影响)。

与最后再对线段树进行维护。

pushdown 

void pushdown(int now)
{
	int lc=tr[now].lc,rc=tr[now].rc;
	tr[lc].lz+=tr[now].lz;
	tr[rc].lz+=tr[now].lz;
	tr[lc].c+=(tr[lc].r-tr[lc].l+1)*tr[now].lz;
	tr[rc].c+=(tr[rc].r-tr[rc].l+1)*tr[now].lz;
	tr[now].lz=0;
}

区间修改代码 

void change(int now,int l,int r,int k)
{
	if(tr[now].l==l && tr[now].r==r)
	{
		tr[now].c+=(r-l+1)*k;
		tr[now].lz+=k;	
		return;
	}
	if(tr[now].lz) pushdown(now);
	int lc=tr[now].lc,rc=tr[now].rc;
	int mid=(tr[now].l+tr[now].r)/2;
	if(r<=mid) change(lc,l,r,k);
	else if(mid+1<=l) change(rc,l,r,k);
	else
	{
		change(lc,l,mid,k);
		change(rc,mid+1,r,k);
	} 
	tr[now].c=tr[lc].c+tr[rc].c;
}

2.查询

与修改差不多,要注意的是每找到一个节点就往下推一次 lazy。

区间查询代码 

ll findsum(int now,int l,int r)
{
	if(tr[now].l==l && tr[now].r==r) return tr[now].c;
	if(tr[now].lz) pushdown(now);
	int lc=tr[now].lc,rc=tr[now].rc;
	int mid=(tr[now].l+tr[now].r)/2;
	if(r<=mid) return findsum(lc,l,r);
	else if(mid+1<=l) return findsum(rc,l,r);
	else return findsum(lc,l,mid)+findsum(rc,mid+1,r);
}

最后,Code 

#include
#include
#define ll long long

struct node{int l,r,lc,rc;ll c,lz;}tr[400010];
int a[100010];
int n,m,len=0;

void bt(int l,int r)
{
	len++;int now=len;
	tr[now].l=l;tr[now].r=r;tr[now].lc=tr[now].rc=-1;tr[now].c=tr[now].lz=0;
	if(l

(温馨提示:记得开 long long)

你可能感兴趣的:(线段树)