老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
有长为 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。
#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。
这样传懒的时候就可以保证正确性了。