Luogu

算法

  • 线段树
  • 数学推导(平均数,方差)

思路

区间操作,很容易想到线段树。
但是方差不好合并,考虑拆解:

s2=1ni=1n(AiA)2=1ni=1n(Ai22Ai2A+A2)=1n(i=1nAi2i=1n2Ai2A+i=1nA2)=1n(i=1nAi22Ai=1nAi+nA2)\large \begin{aligned} s^2 &= \frac{1}{n} \sum_{i = 1}^{n} (A_i - \overline A)^2 \\ &= \frac{1}{n} \sum_{i = 1}^{n} (A_i^2 - 2 \cdot A_i^2 \cdot \overline A + \overline A^2) \\ &= \frac{1}{n} (\sum_{i = 1}^{n} A_i^2 - \sum_{i = 1}^{n} 2 \cdot A_i^2 \cdot \overline A + \sum_{i = 1}^{n} \overline A^2) \\ &= \frac{1}{n} (\sum_{i = 1}^{n} A_i^2 - 2 \cdot \overline A \cdot \sum_{i = 1}^{n} A_i + n \overline A^2) \end{aligned}

所以我们只需要维护区间和 sumsum,区间平方和 sum_squaresum\_square 就可以了。
对于区间加的操作,有如下推导(记 sum2sum2 为区间平方和):

sum2=i=1n(Ai+val)2=i=1n(Ai2+2Aival+val2)=i=1nAi2+i=1n2Aival+i=1nval2=sum2+2vali=1n+nval2\large \begin{aligned} sum2^{\prime} &= \sum_{i = 1}^{n} (A_i + val)^2 \\ &= \sum_{i = 1}^{n} (A_i^2 + 2 \cdot A_i \cdot val + val^2) \\ &= \sum_{i = 1}^{n} A_i^2 + \sum_{i = 1}^{n} 2 \cdot A_i \cdot val + \sum_{i = 1}^{n} val^2 \\ &= sum2 + 2 \cdot val \cdot \sum_{i = 1}^{n} + n \cdot val^2 \end{aligned}

然后我们就做完了。

代码

#include <bits/stdc++.h>

using namespace std;

constexpr int N = 1e5 + 5;
long double arr[N], k;
int n, m, op_type, l, r;

struct SegmentTree {
public:
struct Node {
int l, r;
long double sum_square, sum, add;
};

Node tr[N << 2];

void lazy(int k, long double value) {
tr[k].sum_square += tr[k].sum * 2 * value + (tr[k].r - tr[k].l + 1) * value * value;
tr[k].sum += (tr[k].r - tr[k].l + 1) * value;
tr[k].add += value;
}

void push_up(int k) {
int lc = k << 1, rc = k << 1 | 1;
tr[k].sum_square = tr[lc].sum_square + tr[rc].sum_square;
tr[k].sum = tr[lc].sum + tr[rc].sum;
}

void push_down(int k) {
if (tr[k].add == 0) {
return void();
}

int lc = k << 1, rc = k << 1 | 1;
lazy(lc, tr[k].add);
lazy(rc, tr[k].add);

// ! 注意下传懒标记要清空。
tr[k].add = 0;
}

void build_tree(int k, int l, int r) {
tr[k].l = l, tr[k].r = r;
tr[k].sum = tr[k].sum_square = tr[k].add = 0;

if (tr[k].l == tr[k].r) {
tr[k].sum = arr[l];
tr[k].sum_square = arr[l] * arr[l];
return void();
}

int mid = (tr[k].l + tr[k].r) >> 1;
int lc = k << 1, rc = k << 1 | 1;

build_tree(lc, l, mid);
build_tree(rc, mid + 1, r);

push_up(k);
}

void modify(int k, int l, int r, long double value) {
if (tr[k].l >= l && tr[k].r <= r) {
lazy(k, value);
return void();
}

push_down(k);

int mid = (tr[k].l + tr[k].r) >> 1;
int lc = k << 1, rc = k << 1 | 1;

if (r <= mid) {
modify(lc, l, r, value);
} else if (l > mid) {
modify(rc, l, r, value);
} else {
modify(lc, l, mid, value);
modify(rc, mid + 1, r, value);
}

push_up(k);
}

long double __query_sum(int k, int l, int r) {
if (tr[k].l >= l && tr[k].r <= r) {
return tr[k].sum;
}

push_down(k);

int mid = (tr[k].l + tr[k].r) >> 1;
int lc = k << 1, rc = k << 1 | 1;

if (r <= mid) {
return __query_sum(lc, l, r);
} else if (l > mid) {
return __query_sum(rc, l, r);
} else {
return __query_sum(lc, l, mid) + __query_sum(rc, mid + 1, r);
}
}

long double __query_sum_square(int k, int l, int r) {
if (tr[k].l >= l && tr[k].r <= r) {
return tr[k].sum_square;
}

push_down(k);

int mid = (tr[k].l + tr[k].r) >> 1;
int lc = k << 1, rc = k << 1 | 1;

if (r <= mid) {
return __query_sum_square(lc, l, r);
} else if (l > mid) {
return __query_sum_square(rc, l, r);
} else {
return __query_sum_square(lc, l, mid) + __query_sum_square(rc, mid + 1, r);
}
}

long double query_average(int l, int r) {
long double sum = __query_sum(1, l, r);
return sum / (long double) (r - l + 1);
}

long double query_variance(int l, int r) {
long double average = query_average(l, r);
long double sum = __query_sum(1, l, r);
long double sum_square = __query_sum_square(1, l, r);
int length = r - l + 1;

// cout << average << " " << sum << " " << sum_square << '\n';

return (sum_square - 2 * average * sum + length * average * average) / (long double) length;
}
} segment_tree;

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

cin >> n >> m;

for (int i = 1; i <= n; i ++) {
cin >> arr[i];
}

segment_tree.build_tree(1, 1, n);

while (m --) {
cin >> op_type >> l >> r;

if (op_type == 1) {
cin >> k;
segment_tree.modify(1, l, r, k);
} else if (op_type == 2) {
long double result = segment_tree.query_average(l, r);
result = round(result * 10000) / (long double) 10000;

cout << setiosflags(ios::fixed) << setprecision(4) << result << '\n';
} else if (op_type == 3) {
long double result = segment_tree.query_variance(l, r);
result = round(result * 10000) / (long double) 10000;

cout << setiosflags(ios::fixed) << setprecision(4) << result << '\n';
}
}

return 0;
}