zoukankan      html  css  js  c++  java
  • 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;
    }
    
    
  • 相关阅读:
    HDU 5486 Difference of Clustering 图论
    HDU 5481 Desiderium 动态规划
    hdu 5480 Conturbatio 线段树 单点更新,区间查询最小值
    HDU 5478 Can you find it 随机化 数学
    HDU 5477 A Sweet Journey 水题
    HDU 5476 Explore Track of Point 数学平几
    HDU 5475 An easy problem 线段树
    ZOJ 3829 Known Notation 贪心
    ZOJ 3827 Information Entropy 水题
    zoj 3823 Excavator Contest 构造
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14715848.html
Copyright © 2011-2022 走看看