P2023 [AHOI2009] 维护序列

本文详细解析了线段树结合懒标记的数据结构在处理区间更新和查询问题中的应用,通过具体实例演示了如何使用线段树进行区间乘法、加法操作及求和查询,同时介绍了懒标记的维护和传递机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
有长为 N 的数列,不妨设为 a1,a2,…,aN。
有如下三种操作形式:
把数列中的一段数全部乘一个值;
把数列中的一段数全部加一个值;
询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。

输入格式

第一行两个整数 N 和 P;
第二行含有 N 个非负整数,从左到右依次为 a1,a2,…,aN;
第三行有一个整数 M,表示操作总数;
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作 1:1 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai×c;
操作 2:2 t g c,表示把所有满足 t≤i≤g 的 ai 改为 ai+c;
操作 3:3 t g,询问所有满足 t≤i≤g 的 ai 的和模 P 的值。
同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。

输出格式

对每个操作 3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。

数据范围

1≤N,M≤105,
1≤t≤g≤N,
0≤c,ai≤109,
1≤P≤109

输入样例
7 43
1 2 3 4 5 6 7
5
1 2 5 5
3 2 4
2 3 7 9
3 1 3
3 4 7
输出样例
2
35
8

样例解释

初始时数列为 {1,2,3,4,5,6,7};
经过第 1 次操作后,数列为 {1,10,15,20,25,6,7};
对第 2 次操作,和为 10+15+20=45,模 43 的结果是 2;
经过第 3 次操作后,数列为 {1,10,24,29,34,15,16};
对第 4 次操作,和为 1+10+24=35,模 43 的结果是 35;
对第 5 次操作,和为 29+34+15+16=94,模 43 的结果是 8。

题目分析

这是一道线段树加懒标记的模板题。用线段树来维护区间和,并加入两个懒标记add和mul来维护1 2操作即可。
注意: 懒标记向下传递时是先乘在加(这样方便计算)

代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <stack>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <iomanip>
#define LL long long
#define PII pair<int,int>
using namespace std;
const int N=1e5+5;
struct Node{
	int l,r;
	int sum,add,mul;	//辅助信息:sum(区间和)
}tr[N*4];				//懒标记:add(给区间加一个数),mul(给区间乘一个数)
int n,m,p;
int a[N];
void pushup(int u)
{	//两个子段的sum相加即为父段的sum
	tr[u].sum=(tr[u<<1].sum+tr[u<<1|1].sum)%p;
}
void eval(Node &u,int add,int mul)	//将父段的懒标记传递到子段
{
	u.add=((LL)u.add*mul+add)%p;	//先处理乘法,再处理加法
	u.mul=(LL)u.mul*mul%p;
	u.sum=((LL)u.sum*mul+(LL)add*(u.r-u.l+1))%p;
}
void pushdown(int u)
{
	eval(tr[u<<1],tr[u].add,tr[u].mul);		//将父段的懒标记分别传递到两个子段上
	eval(tr[u<<1|1],tr[u].add,tr[u].mul);
	tr[u].add=0,tr[u].mul=1;			//清空父段的懒标记
}
void build(int u,int l,int r)		//建树
{
	if(l==r) tr[u]={l,r,a[l],0,1};
	else
	{
		tr[u]={l,r,0,0,1};
		int mid=l+r>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void update(int u,int l,int r,int add,int mul)	//区间修改
{
	if(l<=tr[u].l&&tr[u].r<=r) eval(tr[u],add,mul);
	else
	{
	    pushdown(u);
	    int mid=tr[u].l+tr[u].r>>1;
    	if(l<=mid) update(u<<1,l,r,add,mul);
	    if(mid<r) update(u<<1|1,l,r,add,mul);
	    pushup(u);
	}
}
int query(int u,int l,int r)	//区间和查询
{
	if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
	
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	LL sum=0; 
	if(l<=mid) sum=query(u<<1,l,r);
	if(r>mid) sum=(sum+query(u<<1|1,l,r))%p;
	return sum;
}
int main()
{
	scanf("%d %d",&n,&p);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	
	build(1,1,n);
	scanf("%d",&m);
	while(m--)
	{
		int op,l,r,d;
		scanf("%d %d %d",&op,&l,&r);
		if(op==1)
		{
			scanf("%d",&d);
			update(1,l,r,0,d);
		}
		else if(op==2)
		{
			scanf("%d",&d);
			update(1,l,r,d,1);
		}
		else printf("%d\n",query(1,l,r));
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值