方差——线段树
题目来源
题目描述
蒟蒻HansBug在一本数学书里面发现了一个神奇的数列,包含N个实数。他想算算这个数列的平均数和方差。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示数列中实数的个数和操作的个数。
第二行包含N个实数,其中第i个实数表示数列的第i项。
接下来M行,每行为一条操作,格式为以下两种之一:
操作1:1 x y k ,表示将第x到第y项每项加上k,k为一实数。
操作2:2 x y ,表示求出第x到第y项这一子数列的平均数。
操作3:3 x y ,表示求出第x到第y项这一子数列的方差。
输出格式:
输出包含若干行,每行为一个实数,即依次为每一次操作2或操作3所得的结果(所有结果四舍五入保留4位小数)。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
2 1 4
3 1 5
1 1 1 1
1 2 2 -1
3 1 5
输出样例#1:
3.0000
2.0000
0.8000
解题报告
S2=(x1−x¯)2+(x2−x¯)2+(x3−x¯)2+…+(xn−x¯)n=x21+x22+x23+…+x2n+n⋅x¯−2⋅x¯⋅(x1+x2+x3+…+xn)n
∵(x1+x2+x3+…+xn)n=x¯
∴S2=x21+x22+x23+…+x2nn−x¯2
- 通过上述的化简,我们可以通过区间的平方和区间平均值来计算出当前数列的方差
- 而平均值又可以通过区间和计算得出
- 因此线段树的每一个节点需要保存两个数值,分别是当前区间的平方和和区间和
- 而修改操作需要为当前区间的每一项加上一个特定的值,修改之后的区间平方和又可以通过修改前的区间和与区间平方和计算得出
源代码
#include <iostream>
#include <cstdio>
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define root 1, n, 1
using namespace std;
int n, m;
double col[100000 << 2];
struct Node {
double square; //区间平方和
double sum; //区间和
} nodes[100000 << 2];
void PushUP(int rt) {
nodes[rt].square = nodes[rt << 1].square + nodes[rt << 1 | 1].square; //区间平方和相加
nodes[rt].sum = nodes[rt << 1].sum + nodes[rt << 1 | 1].sum; //区间和相加
}
void PushDown(int rt, int m) {
if (col[rt]) { //下放标记
col[rt << 1] += col[rt];
col[rt << 1 | 1] += col[rt];
nodes[rt << 1].square += (m - (m >> 1)) * col[rt] * col[rt] + 2 * nodes[rt << 1].sum * col[rt]; //计算左区间平方和
nodes[rt << 1].sum += (m - (m >> 1)) * col[rt]; //计算左区间和
nodes[rt << 1 | 1].square += (m >> 1) * col[rt] * col[rt] + 2 * nodes[rt << 1 | 1].sum * col[rt]; //计算右区间平方和
nodes[rt << 1 | 1].sum += (m >> 1) * col[rt]; //计算右区间和
col[rt] = 0; //释放标记
}
}
void build(int l, int r, int rt) { //构建线段树
if (l == r) {
scanf("%lf", &nodes[rt].sum);
nodes[rt].square = nodes[rt].sum * nodes[rt].sum;
return;
}
int m = (l + r) >> 1;
build(lson); //构建左子树
build(rson); //构建右子树
PushUP(rt);
}
void update(int L, int R, double add, int l, int r, int rt) { //区间增加
if (L <= l && r <= R) {
col[rt] += add; //添加标记
nodes[rt].square += (r - l + 1) * add * add + 2 * nodes[rt].sum * add; //计算区间平方和
nodes[rt].sum += (r - l + 1) * add; //计算区间和
return;
}
PushDown(rt, r - l + 1); //下放标记
int m = (l + r) >> 1;
if (L <= m)
update(L, R, add, lson);
if (m < R)
update(L, R, add, rson);
PushUP(rt);
}
double sum_query(int L, int R, int l, int r, int rt) { //计算指定区间的区间和
if (L <= l && r <= R)
return nodes[rt].sum;
PushDown(rt, r - l + 1);
double sum = 0;
int m = (l + r) >> 1;
if (L <= m)
sum = sum_query(L, R, lson);
if (m < R)
sum += sum_query(L, R, rson);
return sum;
}
double square_query(int L, int R, int l, int r, int rt) { //计算指定区间的平方和
if (L <= l && r <= R)
return nodes[rt].square;
PushDown(rt, r - l + 1);
double sum = 0;
int m = (r + l) >> 1;
if (L <= m)
sum = square_query(L, R, lson);
if (m < R)
sum += square_query(L, R, rson);
return sum;
}
double count_average(int l, int r) { //返回对应区间的平均值
return sum_query(l, r, root) / (r - l + 1);
}
double count_variance(int l, int r) { //返回对应区间的方差
return (square_query(l, r, root) / (r - l + 1) - count_average(l, r) * count_average(l, r)); //通过区间对应的平方和和绝对值计算区间方差
}
int main() {
freopen("in.txt", "r", stdin);
scanf("%d%d", &n, &m);
build(root);
int a, b, c;
double d;
while (m--) {
scanf("%d%d%d", &a, &b, &c);
if (a == 1) {
scanf("%lf", &d);
update(b, c, d, root);
} else if (a == 2)
printf("%.4f\n", count_average(b, c));
else
printf("%.4f\n", count_variance(b, c));
}
return 0;
}