洛谷 P1471 方差

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;
}

End

posted @ 2021-10-15 21:59  xixike  阅读(52)  评论(0编辑  收藏  举报