「AHOI2009」维护序列题解
「AHOI2009」维护序列题解
文件大小2.9KB是什么啊!!!
题目大意:
对于每个操作:
1 t g c
表示把区间[t,g] * c
;
2 t g c
表示把区间[t,g] + c
;
3 t g
表示询问[t,g]
的区间和模P的值 。
题目思路
对于操作2,就是树状数组的区间加法,重点在操作1,区间乘法怎么求。
建树
众所周知,对于区间加法,我们可以增加一个延迟标记lazy[]
用来存储一个区间是否需要修改。类似地,如果我们的线段树有两个修改操作的话,我们可以再添加一个延迟标记lazyc[]
表示乘法(加法为lazyj[]
),并相应的在建树的时候初始化:
int a[5000005]/*原数组*/, s[5000005]/*线段树*/, lazyj[5000005]/*加法标记*/, lazyc[5000005]/*乘法标记*/;
void build(int k, int l, int r)
{
lazyc[k] = 1;//乘法要初始化为1
lazyj[k] = 0;
if (l == r)
{
s[k] = a[l];
return;
}
int mid = l + r >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
s[k] = s[k << 1] + s[k << 1 | 1];
}
下传
建树完成之后,我们就需要对应地标记下传。那么问题又来了,对于同一个区间,如果既有加法运算,又进行了乘法运算的话,在进行乘法的运算中,它会影响加法(例如区间[L,R]
既加了10,[L-2,R+2]
又乘了10,那么加过10的区间[L,R]
就会经过两种运算),那么对于lazyc[]
和lazyj[]
(加法和乘法)我们应该先下传那一个呢?
对于lazyc[]
它只存储乘法,加法与它无关,所以顺序的关键就是lazyj[]
!
我们不妨来模拟一下:
如果先下传加法的话,那么lazyj[]
就会先加后乘,我们设加上的数为j
,乘的数为c
。那么下传后的lazyj[k]
就会变为\((lazyj[k] + j) * c\),对于没进行加法运算的区间\(lazyj[k]*c\)的差值就是\(j-1(lazyj[c])\),而这显然不是我们想要的。
如果先下传加法的话,那么lazyj[]
就会先乘后加,我们同样设加上的数为j
,乘的数为c
。那么下传后的lazyj[k]
就会变为\(lazyj[k]*c+j\),对于没进行加法运算的区间\(lazyj[k]*c\)的差值就是\(j\),这才是真正的差值。
void pushdown(int k, int l, int r)
{
if (lazyc[k] != 1)//如果lazyc[k]没有进行过标记
{
//修改左子树
s[k << 1] = (s[k << 1] * lazyc[k]) % mod;
lazyc[k << 1] = (lazyc[k << 1] * lazyc[k]) % mod;
lazyj[k << 1] = (lazyj[k << 1] * lazyc[k]) % mod;//对于加法的影响
//修改右子树
s[k << 1 | 1] = (s[k << 1 | 1] * lazyc[k]) % mod;
lazyc[k << 1 | 1] = (lazyc[k << 1 | 1] * lazyc[k]) % mod;
lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] * lazyc[k]) % mod;//对于加法的影响
lazyc[k] = 1;//当前值归1
}
int mid = l + r >> 1;
if (lazyj[k] != 0)//如果lazyj[k]没有进行过标记
{
//修改左子树
s[k << 1] = (s[k << 1] + (mid - l + 1) * lazyj[k]) % mod;
lazyj[k << 1] = (lazyj[k << 1] + lazyj[k]) % mod;
//修改右子树
s[k << 1 | 1] = (s[k << 1 | 1] + (r - mid) * lazyj[k]) % mod;
lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] + lazyj[k]) % mod;
lazyj[k] = 0;//当前值归0
}
}
更新
然后就是更新操作因为题目右两种修改操作,如果把加法和乘法分别写一个函数的话太麻烦,我们可以在原本的update
函数里面添加一个标记f
,用于标记当前操作是加法还是乘法。
void update(int k, int l, int r, int x, int y, int v, int f)
{
if (l > y || r < x)//不在区间内,直接返回
{
return;
}
if (x <= l && r <= y)
{
if (f == 0)//加法
{
s[k] = ((s[k] % mod) + (r - l + 1) * (v % mod)) % mod;//因为要对mod取余,所以不能用+=
lazyj[k] += v;
}
else//乘法
{
s[k] = ((s[k] % mod) * (v % mod)) % mod;//同理
lazyc[k] = (lazyc[k] * v) % mod;
lazyj[k] = (lazyj[k] * v) % mod;//对于加法的修改
}
return;
}
pushdown(k, l, r);//下传
int mid = l + r >> 1;
update(k << 1, l, mid, x, y, v, f);//更新左子树
update(k << 1 | 1, mid + 1, r, x, y, v, f);//更新右子树
s[k] = ((s[k << 1] % mod) + (s[k << 1 | 1] % mod)) % mod;//更新当前区间和
}
查询
查询操作与原来没有区别
int query(int k, int l, int r, int x, int y)
{
if (l > y || r < x)//不在区间内,直接返回0
{
return 0;
}
if (x <= l && r <= y)//已经覆盖,返回当前值
{
return s[k];
}
pushdown(k, l, r);//下传
int mid = l + r >> 1, ans = 0;
return (query(k << 1, l, mid, x, y) + query(k << 1 | 1, mid + 1, r, x, y)) % mod;//统计左子树和右子树的和
}
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, mod, T, a[5000005]/*原数组*/, s[5000005]/*树状数组*/, lazyj[5000005]/*加法标记*/, lazyc[5000005]/*乘法标记*/;
void build(int k, int l, int r)
{
lazyc[k] = 1;
lazyj[k] = 0;
//加法初始化为0,乘法为1
if (l == r)
{
s[k] = a[l];
return;
}
int mid = l + r >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
s[k] = s[k << 1] + s[k << 1 | 1];
}
void pushdown(int k, int l, int r)
{
if (lazyc[k] != 1)//乘法
{
//左子树
s[k << 1] = (s[k << 1] * lazyc[k]) % mod;
lazyc[k << 1] = (lazyc[k << 1] * lazyc[k]) % mod;
lazyj[k << 1] = (lazyj[k << 1] * lazyc[k]) % mod;//对应修改加法
//右子树
s[k << 1 | 1] = (s[k << 1 | 1] * lazyc[k]) % mod;
lazyc[k << 1 | 1] = (lazyc[k << 1 | 1] * lazyc[k]) % mod;
lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] * lazyc[k]) % mod;//对应修改加法
//当前值归1
lazyc[k] = 1;
}
int mid = l + r >> 1;
if (lazyj[k] != 0)//加法
{
//左子树
s[k << 1] = (s[k << 1] + (mid - l + 1) * lazyj[k]) % mod;
lazyj[k << 1] = (lazyj[k << 1] + lazyj[k]) % mod;
//右子树
s[k << 1 | 1] = (s[k << 1 | 1] + (r - mid) * lazyj[k]) % mod;
lazyj[k << 1 | 1] = (lazyj[k << 1 | 1] + lazyj[k]) % mod;
//当前值归0
lazyj[k] = 0;
}
}
void update(int k, int l, int r, int x, int y, int v, int f)
{
if (l > y || r < x)
{
return;
}
if (x <= l && r <= y)
{
if (f == 0)//加法
{
s[k] = ((s[k] % mod) + (r - l + 1) * (v % mod)) % mod;
lazyj[k] += v;
}
else//乘法
{
s[k] = ((s[k] % mod) * (v % mod)) % mod;
lazyc[k] = (lazyc[k] * v) % mod;
lazyj[k] = (lazyj[k] * v) % mod;
}
return;
}
pushdown(k, l, r);//下传
int mid = l + r >> 1;
update(k << 1, l, mid, x, y, v, f);//更新左子树
update(k << 1 | 1, mid + 1, r, x, y, v, f);//更新右子树
s[k] = ((s[k << 1] % mod) + (s[k << 1 | 1] % mod)) % mod;//更新当前区间值和
}
int query(int k, int l, int r, int x, int y)
{
if (l > y || r < x)
{
return 0;
}
if (x <= l && r <= y)
{
return s[k];
}
pushdown(k, l, r);
int mid = l + r >> 1, ans = 0;
return (query(k << 1, l, mid, x, y) + query(k << 1 | 1, mid + 1, r, x, y)) % mod;//统计左子树+右子树
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> mod;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
build(1, 1, n);
cin >> T;
while (T--)
{
int q, t, g, c;
cin >> q;
switch (q)
{
case 1:
cin >> t >> g >> c;
update(1, 1, n, t, g, c, 1);//传1为乘法
break;
case 2:
cin >> t >> g >> c;
update(1, 1, n, t, g, c, 0);//传0为加法
break;
case 3:
cin >> t >> g;
cout << query(1, 1, n, t, g) << "\n";//查询区间
break;
}
}
return 0;
}