Description

​洛谷传送门​

Solution

一道非常经典的线段树问题,下面我们来分析一下。

题目要求我们支持区间加,求区间平均值,求区间方差。

区间加和区间平均值都很简单,唯独这个区间方差要如何维护呢?

我们先来看一下方差的式子:


\[len = r - l + 1\]


\[S^2 = \frac{1}{len}\sum\limits_{i=l}^r{(a_i - \overline a)^2}\]


把平方拆开:


\[S^2 = \frac{1}{len}\sum\limits_{i=l}^r{(a_i^2 - 2a_i\overline a + \overline a^2)}\]


把 \(\sum\) 拆开:


\[S^2 = \frac{1}{len}(\sum\limits_{i=l}^r{a_i^2} - 2\overline a\sum\limits_{i=l}^r{a_i} + \sum\limits_{i=l}^r{\overline a^2})\]


然后我们发现 \(\overline a\) 就是我们询问平均值时计算出来的数,所以我们只需要再维护一个区间平方和就可以 \(O(logn)\) 内计算出答案。

那么如何维护区间平方和呢?

再来看下面的式子:


\[(a + b) ^ 2 = a ^ 2 + 2ab + b ^ 2\]


这是个人都知道。

当操作范围变成一个区间时……


\[s_2\,^2 = \sum\limits_{i = l}^r{(a_i + b)^2}\]


同样把括号拆开:


\[s_2\,^2 = \sum\limits_{i = l}^r{(a_i^2 + 2a_ib + b^2)}\]


然后拆 \(\sum\) :


\[s_2\,^2 = \sum\limits_{i = l}^ra_i^2 + 2b\sum\limits_{i = l}^ra_i + \sum\limits_{i = l}^rb^2\]


再来看这时我们已知什么:


\[sum = \sum\limits_{i = l}^ra_i\]


\[s_1\,^2 = \sum\limits_{i = l}^r{a_i^2}\]


于是 \(s_2\,^2\) 就变成了:


\[s_2\,^2 = s_1\,^2 + 2 * b * sum + b^2 * len\]


这样区间平方和就可以维护了。

最后计算方差的式子我就不多写了。自己稍微推一下吧(其实也没什么了)。

实在不会的话看代码应该也可以理解。

Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#define ls rt << 1
#define rs rt << 1 | 1

using namespace std;

const int N = 1e5 + 10;
int n, m;
double a[N], sum[N << 2], sum2[N << 2];
double lazy[N << 2];

inline void pushup(int rt){
sum[rt] = sum[ls] + sum[rs];
sum2[rt] = sum2[ls] + sum2[rs];
}

inline void pushdown(int l, int r, int rt){
if(lazy[rt]){
int mid = (l + r) >> 1;
sum2[ls] += lazy[rt] * lazy[rt] * (mid - l + 1) + 2 * sum[ls] * lazy[rt];
sum2[rs] += lazy[rt] * lazy[rt] * (r - mid) + 2 * sum[rs] * lazy[rt];
sum[ls] += lazy[rt] * (mid - l + 1);
sum[rs] += lazy[rt] * (r - mid);
lazy[ls] += lazy[rt];
lazy[rs] += lazy[rt];
lazy[rt] = 0;
}
}

inline void build(int l, int r, int rt){
if(l == r){
sum[rt] = a[l];
sum2[rt] = a[l] * a[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(rt);
}

inline void update(int L, int R, double k, int l, int r, int rt){
if(L <= l && r <= R){
sum2[rt] += k * k * (r - l + 1) + 2 * sum[rt] * k;
sum[rt] += k * (r - l + 1);
lazy[rt] += k;
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
if(L <= mid) update(L, R, k, l, mid, ls);
if(R > mid) update(L, R, k, mid + 1, r, rs);
pushup(rt);
}

inline double query_1(int L, int R, int l, int r, int rt){//查询一次和
if(L <= l && r <= R)
return sum[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
double res = 0;
if(L <= mid) res += query_1(L, R, l, mid, ls);
if(R > mid) res += query_1(L, R, mid + 1, r, rs);
return res;
}

inline double query_2(int L, int R, int l, int r, int rt){//查询二次(平方)和
if(L <= l && r <= R)
return sum2[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
double res = 0;
if(L <= mid) res += query_2(L, R, l, mid, ls);
if(R > mid) res += query_2(L, R, mid + 1, r, rs);
return res;
}

signed main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lf", &a[i]);
build(1, n, 1);
for(int i = 1; i <= m; i++){
int op, l, r;
double k;
scanf("%d%d%d", &op, &l, &r);
if(op == 1){
scanf("%lf", &k);
update(l, r, k, 1, n, 1);
}else if(op == 2) printf("%.4lf\n", query_1(l, r, 1, n, 1) / (r - l + 1));
else{
double sum = query_1(l, r, 1, n, 1);//一次和
double avg = sum / (r - l + 1);//平均数
double res = query_2(l, r, 1, n, 1);//二次(平方)和
printf("%.4lf\n", (res - 2 * sum * avg + avg * avg * (r - l + 1)) / (r - l + 1));
}
}
return 0;
}