1. 程式人生 > 實用技巧 >Luogu P1471 方差

Luogu P1471 方差

思路

現在進入正題。這道題要求我們維護區間平均數和區間方差。很顯然,區間平均數是很好維護的,我們只要維護一下區間和,在求平均數的時候用區間和除以區間長度即可。那麼我們怎麼來維護方差呢?很

明顯直接維護是不現實的,所以我們要對方差的公式進行推導:

利用我們最後推出的這個公式,我們可以看出我們只要在多維護一個平方和,就可以輕鬆地求出方差。維護區間平方和的方法和維護區間和的方法沒什麼區別,只是把每一項的值改成了平方再相加。如果還

是不懂的話等下看程式碼吧。

最後還有一個問題就是如何高效地更新區間平方和。一樣,我們還是要推式子:

這樣的話我們就可以利用原本的平方和和原本的區間和來完成對區間平方和的更新操作。更多的細節看程式碼吧。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#define MAXN 100010
typedef long long ll;
int n, m;
double a[MAXN];
struct node{
    double val_sum,val_squ;//sum維護的是區間和,squ維護區間平方和
    double tag;//懶標記
} tree[MAXN << 2];
inline int lson(int k) { return k << 1; }
inline int rson(int k) { return k << 1 | 1; }
inline void push_up(int k){
    tree[k].val_sum = tree[lson(k)].val_sum + tree[rson(k)].val_sum;
    tree[k].val_squ = tree[lson(k)].val_squ + tree[rson(k)].val_squ;
    return;//上傳都是照常上傳,沒有什麼特殊的
}
inline void push_down(int k,int l,int r){
    int mid = (l + r) >> 1;
    tree[lson(k)].tag += tree[k].tag;
    tree[rson(k)].tag += tree[k].tag;
    tree[lson(k)].val_squ += 2.0 * tree[k].tag * tree[lson(k)].val_sum + (mid - l + 1) * tree[k].tag * tree[k].tag;
    tree[rson(k)].val_squ += 2.0 * tree[k].tag * tree[rson(k)].val_sum + (r - mid) * tree[k].tag * tree[k].tag;
    tree[lson(k)].val_sum += tree[k].tag * (mid - l + 1);
    tree[rson(k)].val_sum += tree[k].tag * (r - mid);
    tree[k].tag = 0;//這裡的維護區間平方和的操作利用的就是推的公式,不明白的自己套公式就可以了
    return;
}
void build(int k,int l,int r){
    tree[k].tag = 0;
    if(l==r){
        tree[k].val_sum = a[l];
        tree[k].val_squ = a[l] * a[l];//不要忘了平方
        return;
    }
    int mid = (l + r) >> 1;
    build(lson(k), l, mid);
    build(rson(k), mid + 1, r);
    push_up(k);//不再解釋
}
void update(int k,int cl,int cr,int l,int r,double v){
    if(cl<=l&&r<=cr){//這裡的更新也同樣利用的公式,不懂的去上面找公式自己套
        tree[k].val_squ += 2.0 * v * tree[k].val_sum + (r - l + 1) * v * v;
        tree[k].val_sum += (r - l + 1) * v;
        tree[k].tag += v;
        //這裡的更新順序一定要注意!一定是先更新區間平方和再更新區間和
        //因為我們更新區間平方和的時候利用的是更新之前的區間和
        //據說順序寫錯還能得40……
        return;
    }
    push_down(k, l, r);
    int mid = (l + r) >> 1;
    if(cl<=mid)
        update(lson(k), cl, cr, l, mid, v);
    if(cr>mid)
        update(rson(k), cl, cr, mid + 1, r, v);
    push_up(k);//也不再解釋
}
double query_sum(int k,int ql,int qr,int l,int r){//求區間和
    double res = 0;
    if(ql<=l&&r<=qr)
        return tree[k].val_sum;
    push_down(k, l, r);
    int mid = (l + r) >> 1;
    if(ql<=mid)
        res += query_sum(lson(k), ql, qr, l, mid);
    if(qr>mid)
        res += query_sum(rson(k), ql, qr, mid + 1, r);
    return res;//也不再解釋
}
double query_squ(int k,int ql,int qr,int l,int r){//求區間平方和
    double res = 0;
    if(ql<=l&&r<=qr)
        return tree[k].val_squ;
    push_down(k, l, r);
    int mid = (l + r) >> 1;
    if(ql<=mid)
        res += query_squ(lson(k), ql, qr, l, mid);
    if(qr>mid)
        res += query_squ(rson(k), ql, qr, mid + 1, r);
    return res;//也不再解釋
}
int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n;++i)
        scanf("%lf", &a[i]);
    build(1, 1, n);
    for (int i = 1; i <= m;++i){
        int opt = 0;
        int x = 0, y = 0;
        scanf("%d", &opt);
        scanf("%d%d", &x, &y);
        if(opt==1){
            double k = 0;
            scanf("%lf", &k);
            update(1, x, y, 1, n, k);
        }
        if(opt==2){
            double ret = query_sum(1, x, y, 1, n);
            printf("%.4lf\n", ret / (y - x + 1));//求平均數
        }
        if(opt==3){
            double ret0 = query_sum(1, x, y, 1, n) / (y - x + 1) * 1.0;
            double ret1 = query_squ(1, x, y, 1, n) / (y - x + 1) * 1.0;
            printf("%.4lf\n", ret1 - ret0 * ret0);//這裡利用的也是上面的公式
            //不明白的可以自己去套一下
        }
    }
    return 0;
}