1. 程式人生 > 其它 >洛谷 P1471 方差

洛谷 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

本文來自部落格園,作者:{xixike},轉載請註明原文連結:https://www.cnblogs.com/xixike/p/15412887.html