• P5142 区间方差 题解


    题目传送门

    简述题意

    给一段序列,支持区间求方差,单点修改

    前置芝士

    • 线段树/树状数组/分块
    • 逆元
    • 推式子

    Solution

    题目中已经给出了方差的求法,我们这里设 (len) 表示要求的区间长度:

    [d = frac{1}{len} sum_{i = l}^{r} (a_i - overline{a})^2 ]

    看上去不好维护,让我们化简试试:

    [d = frac{1}{len} sum_{i = l}^{r} (a_i^2 - 2 cdot a_i cdot overline{a} + overline{a}^2) ]

    直接利用乘法分配律将其分开:

    [d = frac{1}{len} sum_{i = l}^{r} a_i^2 - frac{1}{len} sum_{i = l}^{r} 2 cdot a_i cdot overline{a} + frac{1}{len} sum_{i = l}^{r} overline{a}^2 ]

    很简单的昂,把无关项提出来,得到:

    [d = frac{1}{len} sum_{i = l}^{r} a_i^2 - 2 cdot overline{a} cdot frac{1}{len} sum_{i = l}^{r} a_i + overline{a}^2 ]

    因为 (overline{a} = frac{1}{len} sum_{i = l}^{r} a_i),所以继续化简可以得到:

    [d = frac{1}{len} sum_{i = l}^{r} a_i^2 - 2 cdot overline{a}^2 + overline{a}^2 ]

    [d = frac{1}{len} sum_{i = l}^{r} a_i^2 - overline{a}^2 ]

    然后直接pia的一下,把线段树扔上去,维护个区间和和区间平方和,连Push_down都不需要写,就做完了。

    但是!!

    我们知道方差经常是分数形式,并且这还是在模意义下。

    那么求出分母的逆元就好了,具体原理见有理数取余

    求逆元有两种方法(我们定义 (a) 的逆元为 (a^{-1})):

    • 费马小定理:(a^{-1} = a^{p - 2} mod {p})

    • 线性求逆元递推公式:
      $inv_i = (p - frac{p}{i}) imes inv_{p mod i} mod p $,证明的话可以看这道题

    Tips:

    • 记得开 long long
    • 取模要彻底。

    剩下的看代码吧。

    Code

    /*
    Work by: Suzt_ilymics
    Problem: P5142 区间方差
    Knowledge: 线段树
    Time: O(nlogn)
    */
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define LL long long
    #define int long long
    #define orz cout<<"lkp AK IOI!"<<endl
    
    using namespace std;
    const int MAXN = 1e5+5;
    const int INF = 1e9+7;
    const int mod = 1e9+7;
    
    int n, m;
    int a[MAXN], inv[MAXN];
    
    int read(){
        int s = 0, f = 0;
        char ch = getchar();
        while(!isdigit(ch))  f |= (ch == '-'), ch = getchar();
        while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
        return f ? -s : s;
    }
    
    namespace Seg{
        #define lson i << 1
        #define rson i << 1 | 1
        struct Tree{ int val, sum; }tree[MAXN << 2];
        void Push_up(int i) { // 更新 
            tree[i].val = (tree[lson].val + tree[rson].val) % mod;
            tree[i].sum = (tree[lson].sum + tree[rson].sum) % mod;
        } 
        void Build(int i, int l, int r) { // 建树 
            if(l == r) { tree[i].val = a[l] % mod, tree[i].sum = a[l] * a[l] % mod; return ; }
            int mid = (l + r) >> 1;
            Build(lson, l, mid), Build(rson, mid + 1, r);
            Push_up(i);
        }
        void Modify(int i, int l, int r, int L, int R, int val_) { // 单点修改,粘的模板所以写的区间修改的形式 
            if(L <= l && r <= R) { tree[i].val = val_ % mod, tree[i].sum = val_ * val_ % mod; return ; }
            int mid = (l + r) >> 1;
            if(mid >= L) Modify(lson, l, mid, L, R, val_);
            if(mid < R) Modify(rson, mid + 1, r, L, R, val_);
            Push_up(i);
        }
        int Query1(int i, int l, int r, int L, int R) { // 区间和 
            if(L <= l && r <= R) return tree[i].val;
            int mid = (l + r) >> 1, ans = 0;
            if(mid >= L) ans += Query1(lson, l, mid, L, R);
            if(mid < R) ans += Query1(rson, mid + 1, r, L, R);
            return ans % mod;
        }
        int Query2(int i, int l, int r, int L, int R) { // 区间平方和 
            if(L <= l && r <= R) return tree[i].sum;
            int mid = (l + r) >> 1, ans = 0;
            if(mid >= L) ans += Query2(lson, l, mid, L, R);
            if(mid < R) ans += Query2(rson, mid + 1, r, L, R);
            return ans % mod;
        }
    }
    
    void Init() {
        inv[0] = inv[1] = 1; // 线性求逆元,注意初始化 
        for(int i = 2; i <= n; ++i) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    }
    
    int Quick_Pow(int x, int p, int mod) { // 快速幂(然而没用到 
        int res = 1;
        while(p) {
            if(p & 1) res = res * x % mod;
            x = x * x % mod;
            p >>= 1;
        }
        return res;
    }
    
    signed main()
    {
        n = read(), m = read();
        Init();
        for(int i = 1; i <= n; ++i) a[i] = read();
        Seg::Build(1, 1, n);
        for(int i = 1, opt, l, r; i <= m; ++i) {
            opt = read(), l = read(), r = read();
            if(opt == 1) Seg::Modify(1, 1, n, l, l, r);
            else {
                int len = r - l + 1;
                int sum1 = Seg::Query1(1, 1, n, l, r) * inv[len] % mod; 
                int sum2 = Seg::Query2(1, 1, n, l, r) * inv[len] % mod;
                printf("%lld
    ", (sum2 - sum1 * sum1 % mod + mod) % mod);
            }
        }
        return 0;
    }
    
    
  • 相关阅读:
    【推广】+发送短信
    【Linux】+文件操作
    【CRT】+文件上传与下载
    【Java】+查看调用关系
    【博客园】+设置
    【Java】+http
    【Postman】
    【Java】+模拟浏览器操作
    【Java】+快速打印数组
    【Java】+字符串
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14715848.html
Copyright © 2020-2023  润新知