【线段树 数学】JZOJ_4231 寻找神格

本文介绍了一种使用线段树数据结构处理数组操作的方法,包括增加、减少部落人数及计算特定区间内人数的总和与方差。通过维护区间和与平方和,文章详细解释了如何高效地更新区间内的数值并计算方差。

题意

给出一个数列,我们对它进行操作。
输入操作符ttt
1、当t=0t=0t=0时,这一行还有两个数aaabbb,表示编号为aaa的部落增加了bbb个人(如果b&lt;0b&lt;0b<0则表示减少了∣b∣|b|b个人)。
2、当t=1t=1t=1时,这一行还有三个数a,b,ca,b,cabc,表示编号为a∼ba\sim bab的部落增加了ccc个人(如果c&lt;0c&lt;0c<0则表示减少了∣c∣|c|c个人)。
3、当t=2t=2t=2时,这一行还有两个数a,b,a,b,ab表示神格询问了编号为a∼ba\sim bab的部落现在的总人数。
4、当t=3t=3t=3时,这一行还有两个数a,b,a,b,ab表示神格询问了编号为a∼ba\sim bab的部落人数的方差。
方差:如果这些数的平均数是aveaveave,那么它们的方差是(x1−ave)2+(x2−ave)2+⋯+(xn−1−ave)2+(xn−ave)2n\frac{(x_1-ave)^2+(x_2-ave)^2+⋯+(x_{n-1}-ave)^2+(x_n-ave)^2}{n}n(x1ave)2+(x2ave)2++(xn1ave)2+(xnave)2

思路

前三个操作是线段树的基础操作,我们着重处理第四个操作。
因为分母就是nnn,所以我们看分子,把它拆成,
x12−2∗x1∗ave+ave2+x22−2∗x2∗ave+ave2+...+xn2−2∗xn∗ave+ave2x1^2-2*x1*ave+ave^2+x2^2-2*x2*ave+ave^2+...+xn^2-2*xn*ave+ave^2x122x1ave+ave2+x222x2ave+ave2+...+xn22xnave+ave2
然后就可以变成,
(x12+x22+...+xn2)−2(x1+x2+...+xn)ave+(数的个数)ave2(x1^2+x2^2+...+xn^2)-2(x1+x2+...+xn)ave+(数的个数)ave^2(x12+x22+...+xn2)2(x1+x2+...+xn)ave+()ave2
因为我们的线段树维护了区间和,也可以求出区间平均值,那么我们还需要维护它们的平方和,那么如果区间操作了我们的平方和该怎么维护呢?
假设一开始我们的平方和为a2+b2+c2...a^2+b^2+c^2...a2+b2+c2...,如果我们给这个区间加上xxx,就变成(a+x)2+(b+x)2+(c+x)2...(a+x)^2+(b+x)^2+(c+x)^2...(a+x)2+(b+x)2+(c+x)2...,我们又可以拆成,
a2+2ax+x2+b2+2bx+x2+c2+2cx+x2a^2+2ax+x^2+b^2+2bx+x^2+c^2+2cx+x^2a2+2ax+x2+b2+2bx+x2+c2+2cx+x2,可以发现比原来的平方和多了
2(a+b+c)x+(数的个数)x22(a+b+c)x+(数的个数)x^22(a+b+c)x+()x2,然后我们就可以直接敲线段树了。

代码

#include<cstdio>

struct node{
	long long l, r, sum, add, pfh;
}tree[400001];
int n, q;
int a[100001];

void build(int p, int l, int r) {
	tree[p].l = l;
	tree[p].r = r;
	if (l == r) {
		tree[p].sum = a[l];
		tree[p].pfh = a[l] * a[l];
		return;
	}
	int mid = (l + r) / 2;
	build(p * 2, l, mid);
	build(p * 2 + 1, mid + 1, r);
	tree[p].sum = tree[p * 2].sum + tree[p * 2 + 1].sum;
	tree[p].pfh = tree[p * 2].pfh + tree[p * 2 + 1].pfh;
}

void spread(int p) {
	if (tree[p].add) {
		tree[p * 2].add += tree[p].add;
		tree[p * 2 + 1].add += tree[p].add;
		tree[p * 2].pfh += 2 * tree[p * 2].sum * tree[p].add + (tree[p * 2].r - tree[p * 2].l + 1) * tree[p].add * tree[p].add;
		tree[p * 2 + 1].pfh += 2 * tree[p * 2 + 1].sum * tree[p].add + (tree[p * 2 + 1].r - tree[p * 2 + 1].l + 1) * tree[p].add * tree[p].add;
		tree[p * 2].sum += tree[p].add * (tree[p * 2].r - tree[p * 2].l + 1);
		tree[p * 2 + 1].sum += tree[p].add * (tree[p * 2 + 1].r - tree[p * 2 + 1].l + 1);
		tree[p].add = 0;
	}
}

void change(int p, int l, int r, int v) {
	if (l <= tree[p].l && tree[p].r <= r) {
		tree[p].pfh += 2 * tree[p].sum * v + (tree[p].r - tree[p].l + 1) * v * v; 
		tree[p].sum += v * (tree[p].r - tree[p].l + 1);
		tree[p].add += v;
		return;
	}
	spread(p);
	int mid = (tree[p].l + tree[p].r) / 2;
	if (l <= mid) change(p * 2, l, r, v);
	if (r > mid) change(p * 2 + 1, l, r, v);
	tree[p].sum = tree[p * 2].sum + tree[p * 2 + 1].sum;
	tree[p].pfh = tree[p * 2].pfh + tree[p * 2 + 1].pfh;
}

long long ask(int p, int l, int r) {
	if (l <= tree[p].l && tree[p].r <= r)
		return tree[p].sum;
	spread(p);
	int mid = (tree[p].l + tree[p].r) / 2;
	long long result = 0;
	if (l <= mid) result += ask(p * 2, l, r);
	if (r > mid) result += ask(p * 2 + 1, l, r);
	return result;
}

long long askpfh(int p, int l, int r) {
	if (l <= tree[p].l && tree[p].r <= r)
		return tree[p].pfh;
	spread(p);
	int mid = (tree[p].l + tree[p].r) / 2;
	long long result = 0;
	if (l <= mid) result += askpfh(p * 2, l, r);
	if (r > mid) result += askpfh(p * 2 + 1, l, r);
	return result;
}

int main() {
	scanf("%d %d", &n, &q);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	build(1, 1, n);
	int op, x, y, z;
	for (; q; q--) {
		scanf("%d", &op);
		if (op == 0) {
			scanf("%d %d", &x, &y);
			change(1, x, x, y);
		} else if (op == 1) {
			scanf("%d %d %d", &x, &y, &z);
			change(1, x, y, z);
		} else if (op == 2) {
			scanf("%d %d", &x, &y);
			printf("%d\n", ask(1, x, y));
		} else if (op == 3) {
			scanf("%d %d", &x, &y);
			long long s1 = askpfh(1, x, y);
			long long s2 = ask(1, x, y);
			double pjs = (double)s2 / (y - x + 1);
			double s3 = s1 - 2 * s2 * pjs + (y - x + 1) * pjs * pjs;
			printf("%.10lf\n", s3 / (y - x + 1));
		}
	}
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值