支持区间修改的树状数组

支持区间修改的树状数组

原理

对于一个数组\(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. 区间查询 \(\text{query}(k)\),1到k之间的前缀和\(\sum_{i=1}^k{a[i]}\)
  2. 单点修改 \(\text{add}(k,x)\), \(a[k]+=x\)

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

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

posted @ 2020-03-04 02:40  AdaMeta730  阅读(204)  评论(0编辑  收藏  举报