HDU4578 kuangbin线段树专题 转换(Transmission)题解

题目描述

多测,给出一个长度为 \(n\) 的序列,初值为 \(0\) ,进行四种操作:

  1. \([l, r]\) 区间每个数加上 \(c\)
  2. \([l, r]\) 区间每个数乘以 \(c\)
  3. \([l, r]\) 区间每个数都等于 \(c\)
  4. 查询 \([l, r]\) 区间的 \(p\) 次方的和。(\(1 ≤ p ≤ 3\)​)

思路

找出问题

做过 Acwing1227.维护序列 这个题的同学,应该都知道 区间乘 和 区间加 这两个操作应该怎样维护,如果没有做过的话,建议先尝试 \(AC\) 维护序列这个题目再来做这个题目。

相比于维护序列,本题多了一个same操作(op = 3),和查询 \(p\) 次方的和,观察到 \(p\) 的数据范围 \(1 ≤ p ≤ 3\),我们可以采取同时维护区间的和、平方和、立方和的方法来解决查询。所以我们现在要解决两个问题:

  1. 如何在有效时间复杂度以内同时维护区间和、平方和、立方和?
  2. 如何进行 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;
}
posted @ 2022-03-08 13:56  Roshin  阅读(78)  评论(0编辑  收藏  举报
-->