洛谷P1471 方差

题目背景

滚粗了的HansBug在收拾旧数学书,然而他发现了什么奇妙的东西。

题目描述

蒟蒻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

说明/提示

洛谷P1471 方差_第1张图片

样例说明:

洛谷P1471 方差_第2张图片

数据规模:

洛谷P1471 方差_第3张图片

 

思路:首先这个题与线段树板子题唯一不同的一点就是要求支持查询方差(平均数可以直接通过和除以区间长度得到),居然从黄题变成了蓝题(不可思议)。那么我们只需要看看多出一个查询方差需要维护什么东西就可以了。首先通过方差公式推导一波:将括号和求和公式拆开,能得到(设t为平均数):

s^2 = [ ( a1 - t ) ^ 2 + ( a2 - t ) ^ 2 + ...... + ( an - t ) ^ 2 ] / n

    = [ a1 ^ 2 + a2 ^ 2 +......+ an ^ 2 - 2 * t * ( a1 + a2 + ...... + an) + n * t ^ 2 ] / n

线段树的模板支持我们得到序列和,平均数和区间长度,那么我们这个时候只需要来维护一下序列的平方和即可。

那么又一个关键的问题来了,如何push_up?

原先的线段树模板我们让整段序列加上k,和实际上就是加了( r - l + 1 ) * k,那么现在依然是每个数加k,平方和怎么变呢?假设区间内的数为 al,al+1,al+2......ar,将每个数加上k,平方和就变成了( al + k ) ^ 2 + (al+1 + k)^ 2 +......+( ar + k ) ^ 2

同样的把它拆开,我们可以得到:

 

原式 = al ^ 2 + al+1 ^ 2 +......+ ar ^ 2 +2 * k * ( al +al+1 +......+ ar ) + ( r - l + 1 ) * k ^ 2

发现前面的原序列平方和我们是维护了的,k我们知道,序列和我们知道,区间长度也知道,这样就可以push_up合并了,这个题就解决了。

 

另外:注意开double!!!

 

代码:

#include
#include
#include
#include
#include
using namespace std;
const int maxn=100005;
double a[maxn];
int n,q;
struct Node{
    double v1,v2,tag;
    int l,r;
    Node *ls,*rs;
    Node(const int L,const int R){
        l=L,r=R;
        if(l==r){
            tag=0;
            v1=a[l];
            v2=a[l]*a[l];//v2表示序列平方和,初始化为平方 
            rs=ls=NULL;
        }
        else{
            tag=0;
            int M=(L+R)>>1;
            ls=new Node(L,M);
            rs=new Node(M+1,R);
            pushup();
        }
    }
    inline void pushup(){
        v1=ls->v1+rs->v1;
        v2=ls->v2+rs->v2;
    }
    inline void pushdown(){
        if(tag==0) return;
        else{
            ls->maketag(tag);
            rs->maketag(tag);
            tag=0;
        }
    }
    inline void maketag(double w){
        v2=v2+(r-l+1)*w*w+2*w*v1;//根据公式计算,注意一定要先更新v2再更新v1,因为更新v2要用到原序列的和 
        v1+=(r-l+1)*w;
        tag+=w;
    }
    inline bool InRange(const int L,const int R){
        return (l>=L)&&(r<=R);
    }
    inline bool OutofRange(const int L,const int R){
        return (l>R)||(r<L);
    }
    inline void upd(const int L,const int R,double w){
        if(InRange(L,R)){
            maketag(w);
        }else if(!OutofRange(L,R)){
            pushdown();
            ls->upd(L,R,w);
            rs->upd(L,R,w);
            pushup();
        }
    }
    double qry(const int L,const int R){
        if(InRange(L,R)){
            return v1;
        }
        if(OutofRange(L,R)){
            return 0;
        }
        else{
            pushdown();
            return ls->qry(L,R)+rs->qry(L,R);
        }
    }
    double qry1(const int L,const int R){
        if(InRange(L,R)){
            return v2;
        }
        if(OutofRange(L,R)){
            return 0;
        }
        else{
            pushdown();
            return ls->qry1(L,R)+rs->qry1(L,R);
        }
    }
};
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++){
        scanf("%lf",a+i);
    }
    Node *rot=new Node(1,n);
    for(int i=1;i<=q;i++){
        int o,x,y;
        double z;
        scanf("%lld%d%d",&o,&x,&y);
        if(x>y){
            swap(x,y);
        }
        if(o==1){
            scanf("%lf",&z);
            rot->upd(x,y,z);
        }
        if(o==2){
            printf("%.4lf\n",(rot->qry(x,y))/(y-x+1));
        }
        if(o==3){
            double s1=rot->qry1(x,y);//平方和 
            double s2=(rot->qry(x,y))/(y-x+1);//平均数
            double s3=rot->qry(x,y);//
            printf("%.4lf\n",((s1-2*s2*s3+(y-x+1)*s2*s2)/(y-x+1)));//根据公式计算 
        }
    }
    return 0;

你可能感兴趣的:(洛谷P1471 方差)