题意:
给定一个长度为 n n n 的数组 a a a,有 m m m 次操作,① 1 , l , r , x 1, l, r, x 1,l,r,x,区间加 x x x;② 2 , l , r , x 2, l, r, x 2,l,r,x,区间乘 x x x;③ 3 , l , r , x 3, l, r, x 3,l,r,x,区间赋值为 x x x;④ 4 , l , r , p 4, l, r, p 4,l,r,p,询问 ∑ i = l r a i p \sum\limits_{i = l}^{r}a_i^p i=l∑raip,答案模 10007 10007 10007。 ( n , m ≤ 1 0 5 , 1 ≤ p ≤ 3 ) (n, m \leq 10^5, 1 \leq p \leq 3) (n,m≤105,1≤p≤3)
链接:
https://vjudge.net/problem/HDU-4578
解题思路:
首先将区间赋值 x x x 转化为区间乘 0 0 0 再加 x x x,只需考虑乘法和加法操作。用线段树维护区间 p p p 次方和,同时维护乘法与加法标记。标记之间可能相互影响,应依次更新,即给定优先级,那么这里假定标记下推是先乘后加。
标记下推的实质是对子区间的区间更新,即下推标记
(
Δ
m
,
Δ
a
)
(\Delta m, \Delta a)
(Δm,Δa),即子区间先乘
Δ
m
\Delta m
Δm 后加
Δ
a
\Delta a
Δa 时的更新。记子区间标记为
(
m
,
a
)
(m, a)
(m,a),则子区间内
a
i
a_i
ai 的实际值记为
x
i
x_i
xi,子区间的子区间当前值记为
y
i
y_i
yi,注意
y
i
y_i
yi 并非实际值,当父结点上的标记完全下推时才被更新为实际值 。首先考虑标记的下放(更新子区间意味着子区间的子区间也应被更新,叠加上懒惰标记),
先乘
Δ
m
:
(
m
y
i
+
a
)
⇒
(
m
y
i
+
a
)
∗
Δ
m
=
(
m
Δ
m
y
i
+
a
Δ
m
)
=
(
m
′
y
i
+
a
′
)
\Delta m: (my_i + a) \Rightarrow (my_i + a) * \Delta m = (m\Delta m y_i + a \Delta m) = (m'y_i + a')
Δm:(myi+a)⇒(myi+a)∗Δm=(mΔmyi+aΔm)=(m′yi+a′)
再加
Δ
a
:
(
m
′
y
i
+
a
′
)
⇒
(
m
′
y
i
+
a
′
)
+
Δ
a
=
(
m
′
y
i
+
a
′
+
Δ
a
)
=
(
m
′
y
i
+
a
′
′
)
\Delta a: (m'y_i + a') \Rightarrow (m'y_i + a') + \Delta a = (m'y_i + a' + \Delta a) = (m'y_i + a'')
Δa:(m′yi+a′)⇒(m′yi+a′)+Δa=(m′yi+a′+Δa)=(m′yi+a′′)
以上是对标记的维护,那么接下来考虑标记下推时对
x
i
x_i
xi 的直接更新,
先乘
Δ
m
:
\Delta m:
Δm:
{
∑
x
i
⇒
∑
(
x
i
∗
Δ
m
)
=
Δ
m
∑
x
i
=
∑
x
i
′
∑
x
i
2
⇒
∑
(
x
i
∗
Δ
m
2
)
=
Δ
m
2
∑
x
i
2
=
∑
x
i
′
2
∑
x
i
3
⇒
∑
(
x
i
∗
Δ
m
3
)
=
Δ
m
3
∑
x
i
3
=
∑
x
i
′
3
\begin{cases} \sum x_i \Rightarrow \sum (x_i * \Delta m) = \Delta m \sum x_i = \sum x_i'\\ \sum x_i^2 \Rightarrow \sum (x_i * \Delta m^2) = \Delta m^2 \sum x_i^2 = \sum x_i'^2\\ \sum x_i^3 \Rightarrow \sum (x_i * \Delta m^3) = \Delta m^3 \sum x_i^3 = \sum x_i'^3\\ \end{cases}
⎩⎪⎨⎪⎧∑xi⇒∑(xi∗Δm)=Δm∑xi=∑xi′∑xi2⇒∑(xi∗Δm2)=Δm2∑xi2=∑xi′2∑xi3⇒∑(xi∗Δm3)=Δm3∑xi3=∑xi′3
再加
Δ
a
:
\Delta a:
Δa:
{
∑
x
i
′
⇒
∑
(
x
i
′
+
Δ
a
)
=
l
e
n
∗
Δ
a
+
∑
x
i
′
∑
x
i
′
2
⇒
∑
(
x
i
′
+
Δ
a
)
2
=
l
e
n
∗
Δ
a
2
+
2
∗
Δ
a
∗
∑
x
i
′
+
∑
x
i
′
2
∑
x
i
′
3
⇒
∑
(
x
i
′
+
Δ
a
)
3
=
…
\begin{cases} \sum x_i' \Rightarrow \sum (x_i' + \Delta a) = len * \Delta a + \sum x_i' \\ \sum x_i'^2 \Rightarrow \sum (x_i' + \Delta a)^2 = len * \Delta a^2 + 2*\Delta a*\sum x_i' + \sum x_i'^2 \\ \sum x_i'^3 \Rightarrow \sum (x_i' + \Delta a)^3 = \dots \end{cases}
⎩⎪⎨⎪⎧∑xi′⇒∑(xi′+Δa)=len∗Δa+∑xi′∑xi′2⇒∑(xi′+Δa)2=len∗Δa2+2∗Δa∗∑xi′+∑xi′2∑xi′3⇒∑(xi′+Δa)3=…
至此,按标记优先级顺序下放,完成了分级的标记维护和区间更新,固然可以一步到位,但分开维护逻辑较为清晰,若不将赋值操作转化,那么赋值标记优先级可以设置为最高。
标记下推实质是对子区间的更新,那么 u p d a t e update update 与 p u s h D o w n pushDown pushDown 中的更新部分,可以写成 m o d i f y modify modify 函数调用, u p d a t e update update 中将 0 0 0 号结点拿出来放上标记,下推给当前结点。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e5 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e4 + 7;
int n, m;
struct SegTree{
int sum1[maxn << 2], sum2[maxn << 2], sum3[maxn << 2], mul[maxn << 2], add[maxn << 2];
void pushUp(int rt){
sum1[rt] = (sum1[lson] + sum1[rson]) % mod;
sum2[rt] = (sum2[lson] + sum2[rson]) % mod;
sum3[rt] = (sum3[lson] + sum3[rson]) % mod;
}
void build(int l, int r, int rt){
mul[rt] = 1, add[rt] = 0;
sum1[rt] = sum2[rt] = sum3[rt] = 0;
if(l == r) return;
int mid = gmid;
build(l, mid, lson);
build(mid + 1, r, rson);
}
void modify(int rt, int son, int len){
int M = mul[rt], A = add[rt];
(mul[son] *= M) %= mod;
(add[son] *= M) %= mod;
(add[son] += A) %= mod;
(sum3[son] *= M * M % mod * M % mod) %= mod;
(sum2[son] *= M * M % mod) %= mod;
(sum1[son] *= M) %= mod;
(sum3[son] += 3 * sum2[son] % mod * A % mod + 3 * sum1[son] % mod * A % mod * A % mod + len * A % mod * A % mod * A % mod) %= mod;
(sum2[son] += 2 * sum1[son] % mod * A % mod + len * A % mod * A % mod) %= mod;
(sum1[son] += len * A % mod) %= mod;
}
void pushDown(int l, int r, int rt){
int mid = gmid;
modify(rt, lson, mid - l + 1);
modify(rt, rson, r - mid);
mul[rt] = 1, add[rt] = 0;
}
void update(int l, int r, int rt, int L, int R, int M, int A){
if(l >= L && r <= R){
mul[0] = M, add[0] = A;
modify(0, rt, r - l + 1);
return;
}
int mid = gmid;
pushDown(l, r, rt);
if(L <= mid) update(l, mid, lson, L, R, M, A);
if(R > mid) update(mid + 1, r, rson, L, R, M, A);
pushUp(rt);
}
int query(int l, int r, int rt, int L, int R, int p){
if(l >= L && r <= R){
return p == 1 ? sum1[rt] : p == 2 ? sum2[rt] : sum3[rt];
}
int mid = gmid, ret = 0;
pushDown(l, r, rt);
if(L <= mid) ret += query(l, mid, lson, L, R, p);
if(R > mid) ret += query(mid + 1, r, rson, L, R, p);
return ret % mod;
}
} tr;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
while(cin >> n >> m && n){
tr.build(1, n, 1);
while(m--){
int opt, x, y, z; cin >> opt >> x >> y >> z;
if(opt == 1){
tr.update(1, n, 1, x, y, 1, z);
}
else if(opt == 2){
tr.update(1, n, 1, x, y, z, 0);
}
else if(opt == 3){
tr.update(1, n, 1, x, y, 0, z);
}
else{
int ret = tr.query(1, n, 1, x, y, z);
cout << ret << "\n";
}
}
}
return 0;;
}