洛谷P3373 【模板】线段树2

在洛谷随便找了一题线段树的模板题,洛谷大牛的题解真是好评~

题目链接

大神题解

题目先给一个数组,然后给出一些操作

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k

操作2: 格式:2 x y k 含义:将区间[x,y]内每个数加上k

操作3: 格式:3 x y 含义:输出区间[x,y]内每个数的和对P取模所得的结果

然后在询问时要输出区间和,要模p;

注意题目里k是要longlong所以区间和,lazy标签之类的都是longlong

先看一下线段树的基本知识;

现在如果给出了一个数组,如果要求一段连续区间和的话,那么容易想到可以使用树状数组,模板是这样的:链接

但是树状数组只能支持单点更新,那么如果题目要求区间更新的话,树状数组就不能胜任了,于是需要线段树;

首先是基本的线段树,大概长这样,每个节点都维护自己节点的一些信息,比如sum值,那么比如叶子节点2,就存储了他自己的sum值就是a[2]的值,比如节点[0,3]节点就存储了0-3节点区间和。

那么它的实现方式是,建立好树之后,每次更新区间的时候,都更新叶子节点,然后自底向上的更新;

但是这样的实现效率比较低,所以我们对每个节点引入了lazy标签,比如现在如果要在0-2的区间上进行add k操作,那么我们就把0-1节点和2节点的lazy标签更新为k,同时把sum值更新,而暂时不用去更新0和1节点的sum值。

这样我们就不用每次更新的时候都去深入到叶子节点O(logn),可以攒足了很多add操作一起更新下去。

 

但是这题不仅有加法操作,还有乘法操作,所以应该需要两个lazy标签,一个标记加法,一个标记乘法;

思路分析用代码来表述吧,首先是节点,我们需要维护sum和lazy标签,注意都是longlong的:

struct node{
	long long sum;
	long long lazyadd,lazymul;
}A[maxx<<2];

然后是建树,建树的过程中把lazy标签要初始化,这里采用的是数组来表示树,因为线段树一定是平衡二叉树,所以基本上开数组空间也不会浪费很多,一般开max值的4倍肯定是够的;

void build(int u,int l,int r){//建树
	A[u].lazymul=1;A[u].lazyadd=0;
	if(l==r) A[u].sum=a[l];
	else{
		int mid=(l+r)/2;
		build(2*u, l, mid);
		build(2*u+1, mid+1, r);
		A[u].sum=A[2*u].sum+A[2*u+1].sum;
	}
	A[u].sum%=p;
	return ;
}

然后是关键的pushdown操作:

void pushdown(int u, int l, int r){//把u节点的lazy标签向下分发
	int mid=(l+r)/2;
//儿子的值=此刻儿子的值*爸爸的乘法lazytag+儿子的区间长度*爸爸的加法lazytag
	A[u*2].sum=(A[u*2].sum*A[u].lazymul+A[u].lazyadd*(mid-l+1))%p;
	A[u*2+1].sum=(A[u*2+1].sum*A[u].lazymul+A[u].lazyadd*(r-mid))%p;//r-(m+1)+1=r-m
//维护的lazytag
	A[u*2].lazymul=(A[u*2].lazymul*A[u].lazymul)%p;
	A[u*2+1].lazymul=(A[u*2+1].lazymul*A[u].lazymul)%p;
	A[u*2].lazyadd=(A[u*2].lazyadd*A[u].lazymul+A[u].lazyadd)%p;
	A[u*2+1].lazyadd=(A[u*2+1].lazyadd*A[u].lazymul+A[u].lazyadd)%p;
//把父节点的lazytag初始化
	A[u].lazymul=1;
	A[u].lazyadd=0;
	return ;
}

这里涉及到一个优先级的问题,当一个节点有两个标签时,优先更新哪个标签,刚开始我想的是对sum值先乘再加 和 先加再乘 的结果应该是不一样的,但是又考虑到,其实只要在做区间更新乘法的时候,把lazyadd标签的值也乘一下,这样就可以先乘再加了;但是如果想要先加后乘的话,比较复杂,就不去使用;在pushdown之后要记得把lazy标签初始化;

然后就是mul乘法和add加法和查询操作,就是要注意multiple的时候要把lazyadd标签也乘上k:

void mul(int u,int sl,int sr,int l,int r,long long k){//对节点u进行乘法更新,点u的左右范围是sl-sr,所需要更新的范围是l-r,乘k
	if(l>sr || r<sl) return ;
	if(l<=sl && r>=sr){
		A[u].sum*=k;
		A[u].lazymul*=k;
		A[u].lazyadd*=k;
		A[u].sum%=p;
		A[u].lazymul%=p;
		A[u].lazyadd%=p;
		return ;
	}
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	mul(2*u, sl, mid, l, r, k);
	mul(2*u+1, mid+1, sr, l, r, k);
	A[u].sum=(A[2*u].sum+A[2*u+1].sum)%p;
	return ;
}

void add(int u,int sl,int sr,int l,int r,long long k){//对节点u进行加法更新,点u的左右范围是sl-sr,所需要更新的范围是l-r,加k
	if(l>sr || r<sl) return ;
	if(l<=sl && r>=sr){
		A[u].sum+=k*(sr-sl+1);
		A[u].lazyadd+=k;
		A[u].sum%=p;
		A[u].lazyadd%=p;
		return ;
	}
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	add(2*u, sl, mid, l, r, k);
	add(2*u+1, mid+1, sr, l, r, k);
	A[u].sum=(A[2*u].sum+A[2*u+1].sum)%p;
	return ;
}

long long query(int u,int sl,int sr,int l,int r){//查询节点u,点u的范围是sl-sr,查询范围l-r内的总和sum
	if(l>sr || r<sl) return 0;
	if(l<=sl && r>=sr) return A[u].sum;
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	return (query(2*u, sl, mid, l, r)+query(2*u+1, mid+1, sr, l, r))%p;
}

AC代码:

#define _CRT_SBCURE_NO_DEPRECATE
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;

const int maxx = 1e5+6;
int n,m,p;
int a[maxx];

struct node{
	long long sum;
	long long lazyadd,lazymul;
}A[maxx<<2];

void build(int u,int l,int r){//建树
	A[u].lazymul=1;A[u].lazyadd=0;
	if(l==r) A[u].sum=a[l];
	else{
		int mid=(l+r)/2;
		build(2*u, l, mid);
		build(2*u+1, mid+1, r);
		A[u].sum=A[2*u].sum+A[2*u+1].sum;
	}
	A[u].sum%=p;
	return ;
}

void pushdown(int u, int l, int r){//把u节点的lazy标签向下分发
	int mid=(l+r)/2;
//儿子的值=此刻儿子的值*爸爸的乘法lazytag+儿子的区间长度*爸爸的加法lazytag
	A[u*2].sum=((A[u*2].sum+A[u].lazyadd*(mid-l+1))*A[u].lazymul)%p;
	A[u*2+1].sum=((A[u*2+1].sum+A[u].lazyadd*(r-mid))*A[u].lazymul)%p;//r-(m+1)+1=r-m
//维护的lazytag
	A[u*2].lazymul=(A[u*2].lazymul*A[u].lazymul)%p;
	A[u*2+1].lazymul=(A[u*2+1].lazymul*A[u].lazymul)%p;
	A[u*2].lazyadd=(A[u*2].lazyadd*A[u].lazymul+A[u].lazyadd)%p;
	A[u*2+1].lazyadd=(A[u*2+1].lazyadd*A[u].lazymul+A[u].lazyadd)%p;
//把父节点的lazytag初始化
	A[u].lazymul=1;
	A[u].lazyadd=0;
	return ;
}



void mul(int u,int sl,int sr,int l,int r,long long k){//对节点u进行乘法更新,点u的左右范围是sl-sr,所需要更新的范围是l-r,乘k
	if(l>sr || r<sl) return ;
	if(l<=sl && r>=sr){
		A[u].sum*=k;
		A[u].lazymul*=k;
		A[u].lazyadd*=k;
		A[u].sum%=p;
		A[u].lazymul%=p;
		A[u].lazyadd%=p;
		return ;
	}
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	mul(2*u, sl, mid, l, r, k);
	mul(2*u+1, mid+1, sr, l, r, k);
	A[u].sum=(A[2*u].sum+A[2*u+1].sum)%p;
	return ;
}

void add(int u,int sl,int sr,int l,int r,long long k){//对节点u进行加法更新,点u的左右范围是sl-sr,所需要更新的范围是l-r,加k
	if(l>sr || r<sl) return ;
	if(l<=sl && r>=sr){
		A[u].sum+=k*(sr-sl+1);
		A[u].lazyadd+=k;
		A[u].sum%=p;
		A[u].lazyadd%=p;
		return ;
	}
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	add(2*u, sl, mid, l, r, k);
	add(2*u+1, mid+1, sr, l, r, k);
	A[u].sum=(A[2*u].sum+A[2*u+1].sum)%p;
	return ;
}

long long query(int u,int sl,int sr,int l,int r){//查询节点u,点u的范围是sl-sr,查询范围l-r内的总和sum
	if(l>sr || r<sl) return 0;
	if(l<=sl && r>=sr) return A[u].sum;
	pushdown(u, sl, sr);
	int mid=(sl+sr)/2;
	return (query(2*u, sl, mid, l, r)+query(2*u+1, mid+1, sr, l, r))%p;
}


int main (){
	std::ios::sync_with_stdio(false);
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,1,n);
	while(m--){
		int x,y,z;
		long long k;
		cin>>z;
		if(z==1){
			cin>>x>>y>>k;
			mul(1,1,n,x,y,k);
		}
		else if(z==2){
			cin>>x>>y>>k;
			add(1,1,n,x,y,k);
		}
		else{
			cin>>x>>y;
			cout<<query(1,1,n,x,y)<<endl;
		}
	}
	return 0;
}

错点:

1.pushdown中一定要注意先乘后加,而且在mul中要把lazyadd标签也乘k;

2.定义要开longlong

3.一定要在过程中取模,不能在输出的时候就取模,所幸 加法和乘法对于取模是自由的,所以无所谓;

猜你喜欢

转载自blog.csdn.net/qq_33982232/article/details/81256373
今日推荐