HDU4578 kuangbin线段树专题 转换(Transmission)题解
题目描述
多测,给出一个长度为 \(n\) 的序列,初值为 \(0\) ,进行四种操作:
- \([l, r]\) 区间每个数加上 \(c\) 、
- \([l, r]\) 区间每个数乘以 \(c\),
- \([l, r]\) 区间每个数都等于 \(c\),
- 查询 \([l, r]\) 区间的 \(p\) 次方的和。(\(1 ≤ p ≤ 3\))
思路
找出问题
做过 Acwing1227.维护序列 这个题的同学,应该都知道 区间乘 和 区间加 这两个操作应该怎样维护,如果没有做过的话,建议先尝试 \(AC\) 维护序列这个题目再来做这个题目。
相比于维护序列,本题多了一个same操作(op = 3),和查询 \(p\) 次方的和,观察到 \(p\) 的数据范围 \(1 ≤ p ≤ 3\),我们可以采取同时维护区间的和、平方和、立方和的方法来解决查询。所以我们现在要解决两个问题:
- 如何在有效时间复杂度以内同时维护区间和、平方和、立方和?
- 如何进行 same 操作,多加一个标记,标记优先性怎么考虑?还是其他的方法?
解决问题
定义懒标记 \(add\) ,\(mul\),用于更新下一层,初始值 \(add = 0, mul = 1\)。 \(u\) 为区间立方和, \(v\) 为区间平方和, \(sum\) 为区间和
数组 \(mul[i]\) = \(mul^i\) , \(add[i] = add^i\)
问题1 区间信息如何维护?
区间和的维护是很容易的,这里就不再叙述了
平方和的维护
根据经验,尝试推导一下平方和的式子,看有没有突破口。
目标序列 \(a_l,a_{l+1},\dots,a_{r}\) 。
平方和 \(A^2 = a_l^2+a_{l+1}^2+\dots+a_{r}^2=\Sigma_{i=l}^ra_i^2\)
针对单个 \(a_i\) 来考虑,\(a_i'\) 为更新后的值:
-
拥有懒标记的 \(a_i\) : \(a_i' = a_i\times mul + add\)
-
平方为: $a_i'^2 = mul^2\times a_i^2 + 2\times mul\times add\times a_i + add^2 $
-
区间平方和为: \(\Sigma_{i=l}^ra_i'^2=mul^2\times \Sigma_{i=l}^ra_i^2 + 2\times mul\times add\times \Sigma_{i=l}^ra_i + (r-l+1)\times add^2\)
C++ 代码
T& rt = tr[u << 1 or u << 1 | 1];
rt.v = (rt.v * mul[2] % p + 2 * rt.sum * add[1] % p * mul[1] % p + len * add[2] % p) % p;
这样我们就可以在 pushdown
函数内轻松实现平方和的维护
立方和的维护
既然平方和的维护可以用公式推导,那立方和可不可以呢?答案是,可以的!
这里希望大家可以动手推一推,原式是 \(\Sigma_{i=l}^r a_i'^3= \Sigma_{i=l}^r(mul\times a_i + add)^3\) ,推导后可以和下面代码验证一下。
C++ 代码
T& rt = tr[u << 1 or u << 1 | 1];
rt.v = (rt.v * mul[2] % p + 2 * rt.sum * add[1] % p * mul[1] % p + len * add[2] % p) % p;
问题2 如何处理 same 操作?
我一开始也很头疼这个,如果单独加一个标记不太弄的清楚优先级的问题。
后来不知道怎么地就想到了,same 操作或许可以等价于 \(add=c,mul=0\) 的操作,这可真是太妙了。观摩了其他大佬的题解后,发现确实可以多开一个标记,优先处理 same 操作的标记,但是 都AC了,就懒得写了
至于其他细节,可以在代码中查看,个人认为这道题的核心就这么多了,在 modify
中的操作和前面的公式推导大同小异。如果还有不清楚的,欢迎在评论区中提问 ^.^,我会第一时间回复~
C++ Code
#include<bits/stdc++.h>
#define ls u << 1
#define rs u << 1 | 1
#pragma GCC optimize(2)
using namespace std;
const int N = 1e5 + 10, p = 10007;
int n, m;
// 是一道练lazy标记非常不错的题目,同时涉及区间加、区间乘、区间same的三个操作
// 区间加和区间乘优先级是 a * mul + add 的形式,这样可以让两个标记互相叠加,比如:
// 进行add操作值为c,则mul = mul, add = add + c,a = a * mul + add
// 进行mul操作值为c, 则mul = mul * c, add = add * c, a = a * mul + c; 这样可以相互叠加
// 取一个巧,进行same操作值为c,可以让mul = 0, add = c,这样更新过后区间所有值都为c,可以其他操作相互叠加
// 以后就统一在pushdown时,用父节点lazy更新子节点的值,再将lazy标记传给子节点这样来做
struct T{
int l, r, sum, v, u;
int mul, add;
void init(){
sum = v = u = 0; // 值代表lazy标记更新完本层后的值
add = 0, mul = 1; // lazy标记用于处理下一层,不对本层做出影响
}
}tr[N << 2];
void pushup(int u){
tr[u].sum = (tr[ls].sum + tr[rs].sum) % p;
tr[u].v = (tr[ls].v + tr[rs].v) % p;
tr[u].u = (tr[ls].u + tr[rs].u) % p;
}
void update(T& rt, int add[], int mul[]){ // 搞清楚pushdown时的更新问题
int len = rt.r - rt.l + 1;
rt.add = (rt.add * mul[1] % p + add[1]) % p;
rt.mul = rt.mul * mul[1] % p;
rt.u = (mul[3] * rt.u % p + 3 * mul[2] * add[1] % p * rt.v % p + 3 * add[2] * mul[1] % p * rt.sum % p + add[3] * len) % p;
rt.v = (rt.v * mul[2] % p + 2 * rt.sum * add[1] % p * mul[1] % p + len * add[2] % p) % p;
rt.sum = (rt.sum * mul[1] % p + len * add[1] % p) % p;
}
void pushdown(int u){ // lazy 标记下放
int len = tr[u].r - tr[u].l + 1;
if(tr[u].add || tr[u].mul != 1){ // if语句,这里出锅了
int ad = tr[u].add, mu = tr[u].mul;
int add[4] = {0, tr[u].add, tr[u].add * tr[u].add % p, tr[u].add * tr[u].add % p * tr[u].add % p}; // add[i] 代表 add 的 i 次方
int mul[4] = {0, tr[u].mul, tr[u].mul * tr[u].mul % p, tr[u].mul * tr[u].mul % p * tr[u].mul % p}; // mul[i] 代表 mul 的 i 次方
update(tr[ls], add, mul);
update(tr[rs], add, mul);
tr[u].add = 0, tr[u].mul = 1;
}
}
void build(int u, int l, int r){
tr[u] = (T){l, r};
if(l == r)
tr[u].init();
else{
int mid = (tr[u].l + tr[u].r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int c, int t){
if(tr[u].l >= l && tr[u].r <= r){ // 注意区间修改的递归出口
int len = tr[u].r - tr[u].l + 1;
if(t == 1){
tr[u].u = (tr[u].u + 3 * c * tr[u].v % p + 3 * c * c % p * tr[u].sum + len * c % p * c % p * c % p) % p;
tr[u].v = (tr[u].v + 2 * c * tr[u].sum % p + len * c % p * c % p) % p;
tr[u].sum = (tr[u].sum + c * len % p) % p;
tr[u].add = (tr[u].add + c) % p;
}
else if(t == 2){
tr[u].add = (tr[u].add * c) % p;
tr[u].sum = tr[u].sum * c % p;
tr[u].v = tr[u].v * c % p * c % p;
tr[u].u = tr[u].u * c % p * c % p * c % p;
tr[u].mul = c * tr[u].mul % p;
}
else if(t == 3){
tr[u].sum = c * len % p;
tr[u].v = c * c % p * len % p;
tr[u].u = c * c % p * c % p * len % p;
tr[u].add = c % p, tr[u].mul = 0;
}
}
else{
pushdown(u); // 递归分裂前 pushdown
int mid = (tr[u].l + tr[u].r) >> 1;
if(l <= mid) modify(ls, l, r, c, t);
if(r > mid) modify(rs, l, r, c, t);
pushup(u);
}
}
int query(int u, int l, int r, int t){
if(tr[u].l >= l && tr[u].r <= r){
if(t == 1) return tr[u].sum;
if(t == 2) return tr[u].v;
if(t == 3) return tr[u].u;
}
pushdown(u); // 递归分裂前pushdown
int mid = (tr[u].l + tr[u].r) >> 1;
int res = 0;
if(l <= mid) res = query(ls, l, r, t);
if(r > mid) res = (res + query(rs, l, r, t)) % p;
return res % p;
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
while(cin >> n >> m, n || m){
build(1, 1, n);
while(m--){
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op < 4)
modify(1, l, r, c, op);
else
cout << query(1, l, r, c) % p << endl;
}
}
return 0;
}