Loading

洛谷 P1471 方差

题目链接

区间加,区间查询,显然的线段树 & 分块
说实话第一眼看到这个题的时候我是很懵的:
线段树每个区间要维护什么呢?

我们定义\(Sum = \sum\limits_{i=1}^n A_i = A_1 + A_2 + \ldots + A_n\)
\(\qquad \quad Pow = \sum\limits_{i=1}^n A_i^2 = A_1^2 + A_2^2 + \ldots + A_n^2\)

还要知道平均数的公式 \(\overline{A} = \cfrac{Sum}{n}\)

先把方差的计算公式展开一下:

\[\begin{align*} \large s^2 &= \cfrac{1}{n} \sum_{i=1}^n\left(A_i - \overline{A}\right)^2\\ &= \cfrac{ \left(A_{1} - \overline{A} \right)^2 + \left(A_{2} - \overline{A} \right)^2 + \ldots + \left(A_{n} - \overline{A} \right)^2}{n} \\ &= \cfrac{\left(A_1^2 + A_2^2 + \ldots + A_n^2\right) - 2\overline{A} \times \left(A_1 + A_2 + \ldots + A_n \right) + n \times \overline{A}^2}{n}\\ &= \cfrac{Pow - 2\overline{A} \times Sum + n \times \overline{A}^2}{n}\\ &= \cfrac{Pow}{n} - 2 \overline{A}^2 + \overline{A}^2\\ &= \cfrac{Pow}{n} - \cfrac{Sum^2}{n^2} \end{align*}\]

而上面的式子可以由 \(Sum\)\(Pow\) 求得(\(n\) 为区间长度),所以我们只要维护每个区间的区间和以及平方和即可。(●'◡'●)

然后我们再来求一下区间修改的式子。

若是给 \(A_1\)\(A_n\) 这段区间加上有个数 \(k\),区间和比较简单就不列式子了,区间平方和就为:

\[\begin{align*} Ans &= (A_1 + k)^2 + (A_{2} + k)^2 + \ldots + (A_{n} + k)^2 \\ &= A_1^2 + A_{2}^2 + \ldots + A_n^2 + 2k(A_1 + A_2 + \ldots + A_n) + n * k^2\\ &= Pow + 2k \cdot Sum + n \cdot k^2\\ &= Pow + k \cdot (2 \times Sum + n \times k) \quad \text{(这步单纯为了让式子好看一点)} \end{align*}\]

然后我们就得到了这道题所要用的所有公式,只需将 \(1 \sim n\) 这个区间推广到题目给的区间 \(l \sim r\) 即可。

于是我们的pushdown(下传懒标记)和add(区间加)函数就很好写了:

inline void add(int l, int r, double k, int node){
    if(l <= t[node].l && t[node].r <= r){
        t[node].lazy += k;
        t[node].pow += (k * t[node].sum * 2) + k * k * (t[node].r - t[node].l + 1); //乘 2 不能用位运算代替,因为是浮点型的;
        t[node].sum += (t[node].r - t[node].l + 1) * k;
        return;
    }
    if(t[node].lazy) pushdown(node);
    int mid = t[node].l + t[node].r >> 1;
    if(l <= mid) add(l, r, k, node << 1);
    if(mid < r) add(l, r, k, node << 1 | 1);
    update(node);
}

inline void pushdown(int node){  //看似很长,其实就是按照上面的公式来的;
    t[node << 1].lazy += t[node].lazy;
    t[node << 1 | 1].lazy += t[node].lazy;
    t[node << 1].pow += (t[node << 1].sum * t[node].lazy * 2) + (t[node << 1].r - t[node << 1].l + 1) * t[node].lazy * t[node].lazy;
    t[node << 1].sum += (t[node << 1].r - t[node << 1].l + 1) * t[node].lazy;
    t[node << 1 | 1].pow += (t[node << 1 | 1].sum * t[node].lazy * 2) + (t[node << 1 | 1].r - t[node << 1 | 1].l + 1) * t[node].lazy * t[node].lazy;
    t[node << 1 | 1].sum += (t[node << 1 | 1].r - t[node << 1 | 1].l + 1) * t[node].lazy;
    t[node].lazy = 0;
}

其中每个节点维护的sumpow与上文中意义一致。

for(int i = 1; i <= m; i++){
    scanf("%d %d %d", &check, &x, &y);
    if(check == 1){
        scanf("%lf", &k);
        add(x, y, k, 1);
    }
    double Average = ask_sum(x, y, 1) / (y - x + 1); //这里的 Average 表示区间的平均值,用变量记录一下减少重复运算;
    if(check == 2){
        printf("%.4lf\n",  Average);
    }
    if(check == 3){
        double Pow = ask_pow(x, y, 1);
	printf("%.4lf\n",  Pow / (y - x + 1) - (Average * Average));
    }
}

由于比较长就在这不放全部代码了,剩下的部分都是线段树的基本操作。
还有唯一一点要注意的就是题目中给的数列为实数,不一定是整数,存数列的数组一定要开double啊!!!


至于文章开头提到的分块太麻烦不想写了

posted @ 2021-05-24 00:42  Last_order  阅读(78)  评论(0)    收藏  举报