P3373 【模板】线段树 2:一个带有乘和加的模板 - 题解
longyitongxue · · 题解
正文前提示
题目传送门
主要思路
其实,就是在线段树
具体做法
Step \mathbf1 :主函数逻辑
读入
主要代码:
LL n,q;
scanf("%lld%lld%lld",&n,&q,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
}
build(1,1,n);//建树
while(q--){
int op;
scanf("%d",&op);
if(op==1){
LL x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,x,y,k,0);//从节点 1 开始,范围是 x ~ y,给每一个数乘 k,给每一个数加 0。
}else if(op==2){
LL x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,x,y,1,k);//从节点 1 开始,范围是 x ~ y,给每一个数乘 1,给每一个数加 k。
}else{
LL x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",query(1,x,y));//从节点 1 开始,范围是 x ~ y,查询区间和。
}
}
Step \mathbf2 :建树
先声明这个节点的范围是
主要代码:
void pushup(LL p){
tr[p].sum=(tr[lc].sum+tr[rc].sum)%m;//把左右ㄦ子的和都累加过来。
}
void build(LL p,LL l,LL r){
tr[p]={l,r,w[r],1,0};
if(l==r)return;
LL mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
Step \mathbf3 :update(更新函数)逻辑
首先,如果越界了,赶快返回;其次,如果当前节点范围完全覆盖需要更新的部分,使用 calc 函数更新线段树后立即返回;接着,下传懒标记;然后,更新左右ㄦ子;最后,把左右ㄦ子的
这里讲一下 calc 函数怎么写。首先,这个函数是用来维护区间和和两个懒标记的。注意:需要先乘后加才能保证精度不丢失,因为先加后乘的话,新的
主要代码:
void calc(node &t/*一定要传引用,不然无法修改 tr 数组*/,LL mul,LL add){
t.sum=(t.sum*mul+(t.r-t.l+1)*add)%m;
t.mul=t.mul*mul%m; //⎫
// 先乘后加,否则精度丢失
// ⎬新的 mul 为 mul × m,新的 add 为 add × m + a。
t.add=(t.add*mul+add)%m;//⎭
}
void pushdown(LL p){//下传懒标记
calc(tr[lc],tr[p].mul,tr[p].add);
calc(tr[rc],tr[p].mul,tr[p].add);
tr[p].mul=1;//⎫
// 清空懒标记
// ⎬
tr[p].add=0;//⎭
}
void update(LL p,LL l,LL r,LL mul,LL add){
if(tr[p].r<l||tr[p].l>r)return;//越界
if(l<=tr[p].l&&tr[p].r<=r){//完全覆盖
calc(tr[p],mul,add);
return;
}
pushdown(p);
update(lc,l,r,mul,add);
update(rc,l,r,mul,add);
pushup(p);
}
Step \mathbf4 :query 查询函数
首先,如果越界了,赶快返回;接着,如果当前节点范围完全覆盖需要更新的部分,返回当前节点的
LL query(LL p,LL l,LL r){
if(tr[p].r<l||tr[p].l>r)return 0;//越界
if(l<=tr[p].l&&tr[p].r<=r){//完全覆盖
return tr[p].sum%m;
}
pushdown(p);
return (query(lc,l,r)+query(rc,l,r))%m;
}
AC 代码:
#include<iostream>
#include<stdio.h>
#define LL long long
#define lc p<<1 //左ㄦ子
#define rc p<<1|1 //右ㄦ子
using namespace std;
LL m,w[100005];
struct node{
LL l,r,sum,mul,add;
}tr[400005];//大小是 w 数组的 4 倍。
void pushup(LL p){
tr[p].sum=(tr[lc].sum+tr[rc].sum)%m;//把左右ㄦ子的和都累加过来。
}
void calc(node &t/*一定要传引用,不然无法修改 tr 数组*/,LL mul,LL add){
t.sum=(t.sum*mul+(t.r-t.l+1)*add)%m;
t.mul=t.mul*mul%m; //⎫
// 先乘后加,否则精度丢失
// ⎬新的 mul 为 mul × m,新的 add 为 add × m + a。
t.add=(t.add*mul+add)%m;//⎭
}
void pushdown(LL p){//下传懒标记
calc(tr[lc],tr[p].mul,tr[p].add);
calc(tr[rc],tr[p].mul,tr[p].add);
tr[p].mul=1;//⎫
// 清空懒标记
// ⎬
tr[p].add=0;//⎭
}
void build(LL p,LL l,LL r){
tr[p]={l,r,w[r],1,0};
if(l==r)return;
LL mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
void update(LL p,LL l,LL r,LL mul,LL add){
if(tr[p].r<l||tr[p].l>r)return;//越界
if(l<=tr[p].l&&tr[p].r<=r){//完全覆盖
calc(tr[p],mul,add);
return;
}
pushdown(p);
update(lc,l,r,mul,add);
update(rc,l,r,mul,add);
pushup(p);
}
LL query(LL p,LL l,LL r){
if(tr[p].r<l||tr[p].l>r)return 0;//越界
if(l<=tr[p].l&&tr[p].r<=r){//完全覆盖
return tr[p].sum%m;
}
pushdown(p);
return (query(lc,l,r)+query(rc,l,r))%m;
}
int main(){
LL n,q;
scanf("%lld%lld%lld",&n,&q,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
}
build(1,1,n);//建树
while(q--){
int op;
scanf("%d",&op);
if(op==1){
LL x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,x,y,k,0);//从节点 1 开始,范围是 x ~ y,给每一个数乘 k,给每一个数加 0。
}else if(op==2){
LL x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,x,y,1,k);//从节点 1 开始,范围是 x ~ y,给每一个数乘 1,给每一个数加 k。
}else{
LL x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",query(1,x,y));//从节点 1 开始,范围是 x ~ y,查询区间和。
}
}
return 0;
}
正文后闲话
其实,这道题你想用暴力枚举过这道题也没问题,注意超时就行。