支持区间修改的树状数组

支持区间修改的树状数组

原理

对于一个数组(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;
}

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

原文地址:https://www.cnblogs.com/adameta/p/12406227.html