zoukankan      html  css  js  c++  java
  • 支持区间修改的树状数组

    支持区间修改的树状数组

    原理

    对于一个数组(a),以及(a)的差分(c),显然有(c[i]=a[i]-a[i-1])
    那么对于数组a的前缀和有
    (sum_{i=1}^n{a_i}=c[1]+(c[1]+c[2])+...(c[1]+c[2]+...+c[n]))
    进一步的:
    (sum_{i=1}^n{a_i}=c[1]*n+c[2]*(n-1)+...+c[n]*(n-n+1))
    展开括号内
    (sum_{i=1}^n{a_i}=c[1]*n+c[2]*n+...+c[n]*n-(c[1]*(1-1)+c[2]*(2-1)+...+c[n]*(n-1)))
    即为
    (sum_{i=1}^n{a_i}=n*sum_{i=1}^n{c[i]}-sum_{i=1}^n{c[i]*(i-1)})
    因此维护一个前缀和数组需要维护两个差分前缀和(c[i])((i-1)*c[i]),对应为维护(sum_{i=1}^n{c[i]})(sum_{i=1}^n{(i-1)*c[i]})
    这里使用两个树状数组对上述差分数组前缀和维护,分别命名为(tr)(tr1)

    实现

    首先明确基本树状数组的两个基本操作:区间查询单调查询。使用树状数组维护前缀和:

    1. 区间查询 ( ext{query}(k)),1到k之间的前缀和(sum_{i=1}^k{a[i]})
    2. 单点修改 ( ext{add}(k,x)), (a[k]+=x)

    那么对于本文中的支持区间修改的树状数组有以下操作:
    1.区间修改 ( ext{add}(l,r,k))(l和r包含),等效于操作( ext{add1(l,x)}),( ext{add1}(r+1,-x))( ext{add2}(l,(l-1)*x)),( ext{add2}(r+1,r*(-x)))(差分性质定义)
    2.区间查询 ,(querysum(k)),等效于操作(k* ext{query1}(k)*k- ext{query2}(k))

    操作

    基本树状数组

    实现了(O(log(n)))单点修改和区间查询。
    支持单点修改,区间查询,单点查询。

    int n, m;
    ll a[maxn];
    ll tr[maxn];  //树状数组1用于维护差分前缀和$sum_{i=1}^n{c[i]}$
    ll tr1[maxn]; //树状数组2用于维护差分前缀和$sum_{i=1}^n{(i-1)*c[i]}$
    int lowbit(int x) { return x & -x; }
    void add(ll tr[], int l, ll k) {
      for (int i = l; i <= n; i += lowbit(i)) tr[i] = (tr[i] + k) % mod;
    }
    ll query(ll tr[], int r) {
      ll res = 0;
      for (int i = r; i; i -= lowbit(i)) res = (res + tr[i]) % mod;
      return res;
    }
    

    初始化

    void init(int nn) {
      n = nn + 2;//防止空间越界
      for (int i = 0; i <= n; i++) tr[i] = tr1[i] = 0;
    }
    

    区间修改

    //[l,r]区间修改+x
    void add(int l, int r, int x) {
      add(tr, l, x);
      add(tr, r + 1, -x);
      add(tr1, l, 1ll * (l - 1) * x);
      add(tr1, r + 1, 1ll * r * (-x));
    }
    

    区间查询

    查询(sum_{i=1}^ka[i]),即查询([1,k])内的前缀和

    //区间查询原数组sum(a[1,k])
    ll querysum(int k) {
      return (1ll * query(tr, k) * k)  - query(tr1, k);
    }
    

    区间修改

    (a[l]...a[r])区间加上(x).

    //[l,r]区间修改+x
    void add(int l, int r, int x) {
      add(tr, l, x);
      add(tr, r + 1, -x);
      add(tr1, l, 1ll * (l - 1) * x);//防止暴int
      add(tr1, r + 1, 1ll * r * (-x));
    }
    

    复杂度分析

    实质是两个树状数组来维护着差分前缀和,其中空间是3倍的区间长度,(O(3*n))
    时间复杂度:
    在以下操作均为(O(log(n)))

    • 区间同加x
    • 区间查询
    • 单点查询
    • 单调同加x

    相比线段树空间复杂度(O(4*n))要小
    时间复杂度相同。

    编程复杂度差不多(都好难orz

    整合模板

    题目传送门

    #define judge
    // Author: oceanlvr
    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    static int faster_iostream = []() {
      std::ios::sync_with_stdio(false);
      std::cin.tie(NULL);
      return 0;
    }();
    const int maxn = 1e6 + 10;
    const int mod = 772002 + 233;
    int n, m;
    ll a[maxn];
    ll tr[maxn];  //树状数组1
    ll tr1[maxn]; //树状数组2
    int lowbit(int x) { return x & -x; }
    void add(ll tr[], int l, ll k) {
      for (int i = l; i <= n; i += lowbit(i)) tr[i] = (tr[i] + k) % mod;
    }
    ll query(ll tr[], int r) {
      ll res = 0;
      for (int i = r; i; i -= lowbit(i)) res = (res + tr[i]) % mod;
      return res;
    }
    //[l,r]区间修改+x
    void add(int l, int r, int x) {
      add(tr, l, x);
      add(tr, r + 1, -x);
      add(tr1, l, 1ll * (l - 1) * x);
      add(tr1, r + 1, (1ll * r * (-x)%mod+mod)%mod);
    }
    //区间查询原数组sum(a[1,k])
    ll querysum(int k) {
      return (((1ll * query(tr, k) * k) % mod - query(tr1, k) % mod) % mod + mod) %
             mod;
    }
    void init(int nn) {
      n = nn + 2;
      for (int i = 0; i <= n; i++) tr[i] = tr1[i] = 0;
    }
    /*------------------------------------------------------------------------------*/
    //按题目要求区间[l,r]修改 [l+1,r]+d,[l,l]+a0,[r+1,r+1]-前面两个的和
    void addad(int l, int r, int a0, int d) {
      add(l, l, a0);                     //单点l上+a0
      if (l + 1 <= r) add(l + 1, r, d);  //区间[l+1,r] +d
      add(r + 1, r + 1,
          (-(a0 + (1ll * (r - l) * d)) % mod + mod) % mod);  //单点r+1 -前面两个的和
    }
    //区间查询原数组sum(a[1,k])
    int queryad(int k) { return querysum(k); }
    
    int op;
    int main() {
    #ifndef judge
      freopen("in.txt", "r", stdin);
      freopen("out.txt", "w", stdout);
    #endif
      cin >> n >> m;
      for (int i = 1; i <= n; i++) cin >> a[i],a[i]%=mod;
      init(n);
      for (int i = 0; i < m; i++) {
        cin >> op;
        if (op == 1) {
          int x, y;
          cin >> x >> y;
          //(y+1)y/2
          //对[x,x+y-1]加上一个-1
          int l = x, r = min(x + y - 1, n);
          addad(l, r, y, -1);
        } else if (op == 2) {
          int x;
          cin >> x;
          cout << (a[x] + queryad(x)) % mod << endl;
        }
      }
    
      return 0;
    }
    

    参考链接:树状数组区间加等差数列

  • 相关阅读:
    从句分析
    artDialog ( v 6.0.2 ) content 参数引入页面 html 内容
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 13 罗马数字转整数
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 12 整数转罗马数字
    Java实现 LeetCode 11 盛最多水的容器
    Java实现 LeetCode 11 盛最多水的容器
  • 原文地址:https://www.cnblogs.com/adameta/p/12406227.html
Copyright © 2011-2022 走看看