区间乘 + 区间加

题目链接

老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。

有长为 N 的数列,不妨设为 a1,a2,…,aN。

有如下三种操作形式:

  1. 把数列中的一段数全部乘一个值;
  2. 把数列中的一段数全部加一个值;
  3. 询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 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。

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'

using namespace std;

typedef pair<int, int> PII;
typedef long long ll;

const int N = 100010;

int n, p, m;
struct Node
{
	int l, r;
	ll sum, mul, add;
}tr[N * 4];
int a[N];

void eval(Node &root, ll mul, ll add)
{
	root.sum = (root.sum * mul + add * (root.r - root.l + 1)) % p;
	root.mul = (root.mul * mul) % p;
	root.add = (root.add * mul + add) % p;
}

void pushup(int u)
{
	tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}

void pushdown(int u)
{
	eval(tr[u << 1], tr[u].mul, tr[u].add);
	eval(tr[u << 1 | 1], tr[u].mul, tr[u].add);
	tr[u].mul = 1, tr[u].add = 0;
}

void build(int u, int l, int r)
{
	if(l == r)tr[u] = {l, r, a[l], 1, 0};
	else
	{
		tr[u] = {l, r, 0, 1, 0};
		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
}

void modify(int u, int l, int r, ll mul, ll add)
{
	if(tr[u].l >= l && tr[u].r <= r)
	{
		eval(tr[u], mul, add);
	}
	else
	{
		pushdown(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if(l <= mid)modify(u << 1, l, r, mul, add);
		if(r > mid)modify(u << 1 | 1, l, r, mul, add);
		pushup(u);
	}
}

ll query(int u, int l, int r)
{
	if(tr[u].l >= l && tr[u].r <= r)return tr[u].sum;
	else
	{
		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 += query(u << 1 | 1, l, r);
		return sum % p;
	}
}

int main()
{
	IOS
	cin >> n >> p;
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
	}
	
	build(1, 1, n);
	
	cin >> m;
	while(m --)
	{
		int op, l, r, d;
		cin >> op >> l >> r;
		if(op == 1)
		{
			cin >> d;
			modify(1, l, r, d, 0);
		}
		else if(op == 2)
		{
			cin >> d;
			modify(1, l, r, 1, d);
		}
		else
		{
			cout << query(1, l, r) << endl;
		}
	}
	
	return 0;
}

一开始的想法是把区间乘当成区间加来计算,但写完后阳历都过不去,后来模拟了一下样例,当成区间加来算的话,区间内每个值加的值应该是一样的,所以不能这样做。

sum * mul + add

(sum * mul + add) * mul + add = sum * mul * mul + add * mul + mul --> mul * mul就是新的mul, add * mul + mul就是新的add。

这样传懒的时候就可以保证正确性了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值