数学+线段树 方差(洛谷 P1471)

P1471 方差

题目描述

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

说明/提示
在这里插入图片描述
这道题很明显是一道线段树了,平均数好求,只要维护区间和就行;难在方差,方差和平均数的关系我不知道,这道题考的就是这个,足以体现数学的重要性;这里贴两张图片就非常明了了,我们只要维护区间和,区间平方和就行了;
把方差展开:
在这里插入图片描述
然后维护一个平方和,那么平方和怎么维护呢?
在这里插入图片描述
代码:

#include<bits/stdc++.h>
using namespace std;
double a[100100];
int q,x,y;
double z;
double sum;//和
struct Node{
	int l,r;
	double w,f,s;//w为和,s为平方和 
}tree[400100];
inline void pp(int k){
	tree[k].w=(tree[k<<1].w+tree[k<<1|1].w);
	tree[k].s=tree[k<<1].s+tree[k<<1|1].s;
}
inline void build(int k,int ll,int rr){
	tree[k].l=ll,tree[k].r=rr,tree[k].f=0.0;
	if(ll==rr){
		tree[k].w=a[ll];
		tree[k].s=a[ll]*a[ll];
		return;
	}
	int m=(ll+rr)>>1;
	build(k<<1,ll,m);
	build(k<<1|1,m+1,rr);
	pp(k);
}
inline void pd(int k){
	if(tree[k].f){
		tree[k<<1].f+=tree[k].f;
		tree[k<<1|1].f+=tree[k].f;
		tree[k<<1].s+=(double)(2*tree[k].f*(tree[k<<1].w)+(tree[k<<1].r-tree[k<<1].l+1)*tree[k].f*tree[k].f);
		tree[k<<1|1].s+=(double)(2*tree[k].f*(tree[k<<1|1].w)+(tree[k<<1|1].r-tree[k<<1|1].l+1)*tree[k].f*tree[k].f);
		tree[k<<1].w+=(double)tree[k].f*(tree[k<<1].r-tree[k<<1].l+1);
		tree[k<<1|1].w+=(double)tree[k].f*(tree[k<<1|1].r-tree[k<<1|1].l+1);
		tree[k].f=0;
		return;
	}
}
inline void change(int k){
	if(tree[k].l>=x&&tree[k].r<=y){
		tree[k].s+=(double)(2*z*(tree[k].w)+(tree[k].r-tree[k].l+1)*z*z);
		tree[k].w+=(double)(tree[k].r-tree[k].l+1)*z;
		tree[k].f+=z;
		return;
	}
	pd(k);
	int m=(tree[k].l+tree[k].r)>>1;
	if(x<=m) change(k<<1);
	if(y>m) change(k<<1|1);
	pp(k);
}
inline void ask1(int k){//区间查询
	if(tree[k].l>=x&&tree[k].r<=y){
		sum+=tree[k].w;
		return;
	}
	pd(k);
	int m=(tree[k].l+tree[k].r)>>1;
	if(x<=m) ask1(k<<1);
	if(y>m) ask1(k<<1|1);
}
inline void ask2(int k){
	if(tree[k].l>=x&&tree[k].r<=y){
		sum+=tree[k].s;
		return;
	}
	pd(k);
	int m=(tree[k].l+tree[k].r)>>1;
	if(x<=m) ask2(k<<1);
	if(y>m) ask2(k<<1|1);
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lf",&a[i]);
	build(1,1,n);
	while(m--){
		scanf("%d",&q);
		if(q==1){
			scanf("%d%d%lf",&x,&y,&z);
			change(1);
		}
		else if(q==2){
			scanf("%d%d",&x,&y);
			sum=0;//和 
			ask1(1);
			printf("%.4lf\n",(double)1.0*sum/(y-x+1));
		}
		else{
			sum=0;//和 
			scanf("%d%d",&x,&y);
			ask1(1);
			double S=(double)1.0*sum/(y-x+1);//平均数 
			sum=0;//平方和 
			ask2(1); 
			printf("%.4lf\n",(double)(sum/(y-x+1)-S*S));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值