洛谷 P1471 方差
阿新 • • 發佈:2021-10-15
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\)
然後我們發現 \(\overline a\) 就是我們詢問平均值時計算出來的數,所以我們只需要再維護一個區間平方和就可以 \(O(logn)\) 內計算出答案。
那麼如何維護區間平方和呢?
再來看下面的式子:
\[(a + b) ^ 2 = a ^ 2 + 2ab + b ^ 2 \]這是個人都知道。
當操作範圍變成一個區間時……
\[s_2\,^2 = \sum\limits_{i = l}^r{(a_i + 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