初识线段树——线段树的一些应用方式


线段树维护的是区间和,并且可以在 logn 的时间内进行区间修改,如果想要利用线段树实现一些事情,就需要将 pushdown 这个函数处理好,懒标记究竟如何在不同的题目背景下传递到子节点是我们需要讨论的。

区间方差和区间平均数这两个数值,我们发现,对于区间平均数来说就是 区间和 除以 区间长度,而方差并不好求。那么我们需要通过方差公式来推出一个可以操作的方法来,接下来将公式的平方部分展开,就出现了一个利用 区间和区间平方数的和 来得到方差的方式

我们利用线段树来维护这个关系,修改操作会使区间内的每个数增加一个值,懒标记会记录这个值,每次打算计算儿子节点时,由于之前太懒了,并没有对儿子节点进行改变,就需要将懒标记下传,将儿子节点也进行改变,也就是进行 pushdown 操作

pushdown 函数的实现,就是儿子节点在懒标记下传时数值改变的方式,例如 : 在此题中,我们会改变 区间和s区间平方数的和s2 ,对于平方和进行改变,与推导出方差如何得到的方式相同,我们需要将公式展开,看将区间的每个数增加一个数,区间平方数的和 会发生什么变化,这样 pushdown 的实现方式就了然了

#include <bits/stdc++.h>
#define ls (p << 1)
#define rs (p << 1 | 1)

using namespace std;

const int N = 1e5 + 10;
int n;
double a[N];
struct Node {
    int l, r;
    double tag, s, s2;
}tr[N << 2];

Node pushup(Node L, Node R) {
    return {L.l, R.r, 0, L.s + R.s, L.s2 + R.s2};
}

void pushup(int p) {
    tr[p] = pushup(tr[ls], tr[rs]);
}

void build(int p, int l, int r) {
    tr[p] = {l, r, 0, 0, 0};
    if (l == r) {
        tr[p].s = a[l], tr[p].s2 = a[l] * a[l];
        return;
    }
    int mid = l + r >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(p);
}

void wow(Node &a, double k) {
    a.s2 += a.s * k * 2 + k * k * (a.r - a.l + 1);
    a.s += k * (a.r - a.l + 1);
    a.tag += k;
}

void pushdown(int p) {
    if (tr[p].tag) {
        wow(tr[ls], tr[p].tag);
        wow(tr[rs], tr[p].tag);
        tr[p].tag = 0;
    }
}

void update(int p, int x, int y, double k) {
    if (x <= tr[p].l && tr[p].r <= y) {
        wow(tr[p], k);
        return;
    }
    pushdown(p);
    int mid = tr[p].l + tr[p].r >> 1;
    if (x <= mid) update(ls, x, y, k);
    if (y > mid) update(rs, x, y, k);
    pushup(p);
}

Node query(int p, int x, int y) {
    if (x <= tr[p].l && tr[p].r <= y) {
        return tr[p];
    }
    pushdown(p);
    int mid = tr[p].l + tr[p].r >> 1;
    if (y <= mid) return query(ls, x, y);
    else if (x > mid) return query(rs, x, y);
    return pushup(query(ls, x, y), query(rs, x, y));
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int m;
    cin >> n >> m;

    for (int i = 1; i <= n; i ++) cin >> a[i];

    build(1, 1, n);
    cout << fixed << setprecision(4);

    while (m --) {
        int op, x, y;
        double k;
        cin >> op >> x >> y;
        if (op == 1) {
            cin >> k;
            update(1, x, y, k);
        } else if (op == 2) {
            auto t = query(1, x, y);
            cout << t.s / (y - x + 1) << '\n';
        } else {
            auto t = query(1, x, y);
            cout << -(t.s / (y - x + 1)) * (t.s / (y - x + 1)) + t.s2 / (y - x + 1) << '\n';
        }
    }

    return 0;
}
posted @   he_jie  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示