题解 P1471 【方差】

· · 题解

#include<iostream>
#include<iomanip>
using namespace std;
const int N = 100005, INF = 0x7fffffff;
double a[N], sum[4*N], sum2[4*N];
double tag[4*N];

void pushup(int k) {
    sum[k] = sum[k * 2] + sum[k * 2 + 1]; //区间和
    sum2[k] = sum2[k * 2] + sum2[k * 2 + 1]; //区间平方和
    return ;
}

void build(int k, int lt, int rt) { //第k个结点, lt rt为要询问的区间范围
    if(lt == rt) {
        sum[k] = a[lt]; //a为原始数组,sum[k]为第k个结点的区间和
        sum2[k] = a[lt] * a[lt]; //平方
        return ;
    }
    int mid = lt + (rt - lt) / 2; // mid = (lt + rt) >> 1
    build(k * 2, lt, mid);
    build(k * 2 + 1, mid + 1, rt);
    pushup(k);
    return ;
}

void Add(int k, int lt, int rt, double val) { //打懒标记
    tag[k] += val; //标记K结点对应的区间要加的值
    sum2[k] += 2 * val * sum[k] + val * val * (rt - lt + 1); //因为平方和要用到 区间和, 所以要先更新!
    sum[k] += (rt - lt + 1) * val;
    return ;
}

void pushdown(int k, int lt, int rt) {
    if(tag[k] == 0)
        return ;
    int mid = lt + (rt - lt) / 2;
    Add(k*2, lt, mid, tag[k]);
    Add(k*2+1, mid+1, rt, tag[k]);
    tag[k] = 0; //将标记传给子节点后,父节点的标记要清零,否则会加重复
    return ;
}

//在qx, qy区间每个数加上val
void modify(int k, int lt, int rt, int qx, int qy, double val) {
    if(lt > qy || rt < qx)
        return ;
    else if(lt >= qx && rt <= qy) {
        Add(k, lt, rt, val);
        return ;
    }
    int mid = lt + (rt - lt) / 2;
    pushdown(k, lt, rt);
    modify(k * 2, lt, mid, qx, qy, val);
    modify(k * 2 + 1 , mid + 1, rt, qx, qy, val);
    pushup(k);
    return ;
}

double query(int k, int lt, int rt, int qx, int qy) {
    if(lt > qy || rt < qx)
        return 0;
    else if(lt >= qx && rt <= qy)
        return sum[k];
    int mid = lt + (rt - lt) / 2;
    pushdown(k, lt, rt); //询问时才下传标记,因为需要知道子区间要加的值
    return query(k * 2, lt, mid, qx, qy) + query(k * 2 + 1, mid + 1, rt, qx, qy);
}

double query2(int k, int lt, int rt, int qx, int qy) {
    if(lt > qy || rt < qx)
        return 0;
    else if(lt >= qx && rt <= qy)
        return sum2[k];
    int mid = lt + (rt - lt) / 2;
    pushdown(k, lt, rt); //询问时才下传标记,因为需要知道子区间要加的值
    return query2(k * 2, lt, mid, qx, qy) + query2(k * 2 + 1, mid + 1, rt, qx, qy);
}

int main() {
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    build(1, 1, n); //建树
    for(int i = 1; i <= m; i++) {
        int option, x, y;
        double v;
        cin >> option;
        if(option == 1) {
            cin >> x >> y >> v;
            modify(1, 1, n, x, y, v);
        } else if(option == 2) {
            cin >> x >> y;
            cout << fixed << setprecision(4) << query(1, 1, n, x, y) / (y-x+1) << "\n";
        } else {
            cin >> x >> y;
            double avg = query(1, 1, n, x, y) / (y-x+1);
            cout << fixed << setprecision(4) << -avg * avg + query2(1, 1, n, x, y) / (y-x+1) << "\n";
        }
    }
    return 0;
}