支持区间修改的树状数组

支持区间修改的树状数组

原理

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

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

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

时间: 2024-08-22 15:19:04

支持区间修改的树状数组的相关文章

树状数组变形:带区间修改的树状数组

原理很简单,利用差分知识做的,只能单点查询,在性能上优于线段树,但没有区间查询功能. 1 #include<bits/stdc++.h> 2 #define f(i,a,b) for(int i=a;i<=b;i++) 3 using namespace std; 4 5 const int N=5e5+5; 6 int n,q,opt,le,ri,pos,val; 7 int a[N]; 8 int tree[N]; 9 #define lowbit(x) (x&(-x)) 1

ACM学习历程—51NOD 1685 第K大区间2(二分 &amp;&amp; 树状数组 &amp;&amp; 中位数)

http://www.51nod.com/contest/problem.html#!problemId=1685 这是这次BSG白山极客挑战赛的E题. 这题可以二分答案t. 关键在于,对于一个t,如何判断它是否能成为第k大. 将序列中大于t的置为1,小于t的置为-1,等于t的置为0.那么区间中位数大于t的和就大于0,小于t的就小于0.于是就是判断区间和大于0的个数是否小于等于k. 维护前缀和sum(i),然后统计之前sum(j)小于sum(i)的有多少个,就是以i为右值的区间和大于0的个数.于

资瓷区间修改+区间求和的树状数组(一维/二维)

一维:令 \(v_i\) 为差分数组,那么 \([0, k]\) 的前缀和就是 \(\sum{v_i(k+1-i)} = (k+1) \cdot \sum{v_i} + \sum{v_i \cdot (-i)}\),树状数组维护一下 \(v_i\) 和 \(v_i \cdot i\) 即可. template <typename I> struct Fenwick { struct Node { I v0, v1; void add(I d0, I d1) { v0 += d0; v1 +=

区间素数个数 树状数组 HIT 1867 经理的烦恼

http://acm.hit.edu.cn/hoj/problem/view?id=1867 经理的烦恼   Source : HCPC 2005 Spring   Time limit : 2 sec   Memory limit : 32 M Submitted : 2994, Accepted : 686 Jerry是一家公司销售部门的经理.这家公司有很多连锁店,编号为1,2,3,... Jerry每天必须关注每家连锁店的商品数量及其变化,一项很乏味的工作.在连锁店比较少的时候,Jerry

FZU2224 An exciting GCD problem 区间gcd预处理+树状数组

分析:(别人写的) 对于所有(l, r)区间,固定右区间,所有(li, r)一共最多只会有log个不同的gcd值, 可以nlogn预处理出所有不同的gcd区间,这样区间是nlogn个,然后对于询问离线处理, 用类似询问区间不同数字的方法,记录每个不同gcd最后出现的位置,然后用树状数组进行维护 注:我是看了这段代码会的,但是他的nlogn预处理我不会,我会nlog^2n的 dp[i][j]代表以i为右端点,向左延伸2^j个点(包括i)的gcd,然后因为这样的gcd满足递减,所以可以二分找区间 代

hdu 1166 敌兵布阵——(区间和)树状数组

here:http://acm.hdu.edu.cn/showproblem.php?pid=1166 Input 第一行一个整数T,表示有T组数据. 每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50). 接下来每行有一条命令,命令有4种形式: (1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30) (2)Sub i j ,i和j为正整数,表示第i

hdu 1166 敌兵布阵——(区间和)树状数组/线段树

here:http://acm.hdu.edu.cn/showproblem.php?pid=1166 Input 第一行一个整数T.表示有T组数据. 每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地.接下来有N个正整数,第i个正整数ai代表第i个工兵营地里開始时有ai个人(1<=ai<=50). 接下来每行有一条命令.命令有4种形式: (1) Add i j,i和j为正整数,表示第i个营地添加j个人(j不超过30) (2)Sub i j ,i和j为正整数,表示第i

吊打线段树的超级树状数组

你是否讨厌线段树那冗长的代码?你是否还在因为线段树的难调试而满头♂dark汗?那么,请不要错过!超级树状数组特价!只要998,只要998! ##¥……#……¥%……&%¥……ER#%$#$#^T%$^$% 超级树状数组,其实是一种能够支持区间修改和区间查询的树状数组,和线段树相比,它的常数极小,不需要太多空间,代码量也少了很多(简直吊打线段树) 1.树状数组 既然是超级树状数组,那么就需要一个树状数组作为基础了.但是在真正实现时,只用到了lowbit()函数(所以说lowbit是树状数组的核心啊

树状数组与差分

目录 树状数组的引入 lowbit的含义 树状数组的前缀和存储方式 单点修改 区间查询 初始化 模板例题--树状数组基本操作 差分--区间修改 备注 @(树状数组算法详解·目录) 树状数组的引入 相信读者一定知道什么是前缀和,形如一串数\(a1,a2...,an,sum[i]=a[1]+a[2]+...+a[i]\) 前缀和在算法的优化上占有很重要的地位,一般就会预先对数据进行预处理运算以后,再在运算过程中用\(O(1)\)时间调用,这样的操作很大程度上避免了实际运算中的枚举,NOIP2016魔