[Luogu P2023][JZOJ1663][AHOI2009]维护序列 题解
题目分析
看题面可以知道题目的要求:
对于一个长度为N的序列a支持以下操作
1.令所有满足l<=i<=r的ai全部变为ai*c。
2.令所有满足l<=i<=r的ai全部变为ai+c
3.求所有满足l<=i<=r的ai的和。
显然这是对一个区间做加法和乘法的操作,可以使用线段树完成。
联想只有区间加法的过程,对于线段树上的一个节点,我们设sum表示该区间的和,inc表示该区间每个数要加上的数,那么该节点所表示的区间和为sum + inc * (r - l + 1)
属于
x+b
这种形式,
(r−l+1)
可以看作常数。
但是现在区间不仅有加法,还有乘法,因此很容易想到区间和的形式应该为:
ax+b
表示现在的区间和是原来的区间和先乘以a再加上b。
在程序中我们把mtp定义为a,sum定义为x,inc定义为b,下传标记时节点的更新就是sum = mtp * sum + inc
区间修改
当我们要修改一个区间时,要保证ax+b的形式,即先乘后加的形式。当将区间乘以一个数k时,原来的区间和为 ax+b ,乘以k得 k(ax+b)=kax+kb ,也就是把节点的inc和mtp都乘上一个k。
区间加一个数更加简单,原来的区间和为 ax+b ,加上一个k为 ax+b+k ,合并b, k得 ax+(b+k) ,也就是把原来的inc加上一个k。
标记下传
我们设要下传标记的节点的inc为
b
,sum为
代码:
#pragma GCC optimize(2) //O2优化
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define lson rt << 1 //简化书写
#define rson rt << 1 | 1
const int N = 200007; //数据范围有误,100000会RE
typedef long long ll;
int n, q, L, R, op;
ll k, P, a[N];
struct SegmentTree
{
ll sum[N << 2], mtp[N << 2], inc[N << 2];
void down(int rt, int l, int r)
{
if (mtp[rt] == 1 && inc[rt] == 0) return; //都为初始状态时无需下传
if (l != r)
mtp[lson] = mtp[lson] * mtp[rt] % P, //儿子的mtp乘以当前节点mtp
mtp[rson] = mtp[rson] * mtp[rt] % P,
inc[lson] = (inc[lson] * mtp[rt] % P + inc[rt]) % P; //儿子的inc乘以当前节点mtp再加当前节点inc
inc[rson] = (inc[rson] * mtp[rt] % P + inc[rt]) % P;
sum[rt] = (sum[rt] * mtp[rt] % P + inc[rt] * (r - l + 1) % P) % P; //更新节点信息
mtp[rt] = 1, inc[rt] = 0; //清空标记
}
void build(int rt, int l, int r) //根据数组构建线段树,当然打insert也是可以的
{
mtp[rt] = 1, inc[rt] = 0; //初始化标记
if (l == r) { sum[rt] = a[l]; return; }
int mid = l + r >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
sum[rt] = sum[lson] + sum[rson];
}
ll qrysum(int rt, int l, int r)
{
down(rt, l, r); //先下传标记
if (L <= l && r <= R) return sum[rt];
ll ret = 0; int mid = l + r >> 1;
if (L <= mid) ret = (ret + qrysum(lson, l, mid)) % P;
if (mid + 1 <= R) ret = (ret + qrysum(rson, mid + 1, r)) % P;
return ret;
}
void rangeplus(int rt, int l, int r)
{
down(rt, l, r); //先下传标记
if (L <= l && r <= R) { inc[rt] = (inc[rt] + k) % P; return; } //区间更新,inc加上k
int mid = l + r >> 1;
if (L <= mid) rangeplus(lson, l, mid);
if (mid + 1 <= R) rangeplus(rson, mid + 1, r);
down(lson, l, mid), down(rson, mid + 1, r); //下传儿子的标记
sum[rt] = (sum[lson] + sum[rson]) % P;
}
void rangemtp(int rt, int l, int r)
{
down(rt, l, r); //先下传标记
if (L <= l && r <= R) { mtp[rt] = mtp[rt] * k % P, inc[rt] = inc[rt] * k % P; return; } //区间更新,mtp和inc都乘以一个k
int mid = l + r >> 1;
if (L <= mid) rangemtp(lson, l, mid);
if (mid + 1 <= R) rangemtp(rson, mid + 1, r);
down(lson, l, mid), down(rson, mid + 1, r); //下传儿子的标记
sum[rt] = (sum[lson] + sum[rson]) % P;
}
} tree;
inline ll read() //读入加速
{
ll x = 0, f = 0;
char c = getchar();
for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + c - '0';
return f ? -x : x;
}
void init()
{
n = read(), P = read();
for (int i = 1; i <= n; i++) *(a + i) = read();
tree.build(1, 1, n);
}
void solve()
{
q = read();
while (q--)
{
op = read();
if (op == 1)
L = read(), R = read(), k = read(),
tree.rangemtp(1, 1, n);
else if (op == 2)
L = read(), R = read(), k = read(),
tree.rangeplus(1, 1, n);
else
L = read(), R = read(),
printf("%lld\n", tree.qrysum(1, 1, n));
}
}
int main()
{
init();
solve();
return 0;
}