回顾树状数组

  树状数组是一种常用的数据结构,能够在O(log2n)的时间内进行单点修改和求前缀和。因为代码量小、常熟小往往在某些应用中快于线段树(当然有些问题是不能呢用树状数组完成的)。

最基本的树状数组

  方法1:用一个数组,O(1)修改, O(n)查询

  方法2:求前缀和,O(n)修改,O(1)查询

  以上两种方法卡一卡就TLE了。

  树状数组就平衡了一下这两种方法,修改的时候多修改一点,查询的时候多算一点。

  为了清楚地明白树状数组是如何运作的,我们实现定义 lowbit(x) 是写成2进制后的x中最后一位1。例如(10)10 = (1010)2,则lowbit(10) = (10)2 = (2)10.

  树状数组本质上就是一个长度为n的数组,但是它的每一个位置管辖另一些位置(就是它们的和),对于一个节点i,它的数据同时会被加在节点(i + lowbit(i))上。例如下面这幅图。

  至于计算这个lowbit通常是用这么一个快捷的方法:

1 #define lowbit(x) ((x) & (-x))

  对于修改操作,就不断去更新管辖当前节点的节点,直到超出范围。

1 inline void add(int idx, LL val) {
2     for(; idx <= s; idx += lowbit(idx))
3         a[idx] += val;
4 }

  现在证明修改操作是O(log2n)的。

  1) 当 idx 写成2进制后,1的个数不为1时,每次 idx += lowbit(idx) 时,至少有1个1进位,显然至多log2idx次这样的操作idx写成二进制后就只有1个1了(1    的个数只会减少不会增加)

  2) 当 idx 写成2进制后,1的个数为1时,显然lowbit(idx) = idx,所以每次加倍,但是又不能超过n,最多进行log2n次这样的操作。

  所以修改操作的时间复杂度时O(log2n)。

  观察上面,可以发现一个有趣的结论,树状数组的一个节点i存的值,恰好是原数组中lowbit(i)个连续的元素和的和。

  所以求前缀和的操作,就向前依次去加它没有算到的原数组的数(不是暴力,是直接加树状数组中节点的值)

1 inline LL getSum(int idx) {
2     LL rt = 0;
3     for(; idx; idx -= lowbit(idx))
4         rt += a[idx];
5     return rt;
6 }

  对于求任意一段区间的和就前缀和减减就好了。

各种乱搞的树状数组

支持区间修改单点查询的树状数组

  这个似乎不是树状数组的本职工作,但还是可以做的。

  差分是一个离线的"算法"吧,能够支持O(1)修改,O(n)查询(但是只能在最后查询)。

  差分数组满足一个性质,就是位置i的前缀和是在它真正的值。(差分数组秋求前缀和后得到的是原数组)

  不懂差分数组的就看张图吧:

  既然差分数组能够快速地进行区间修改(本质上时单点修改),求单点是靠求前缀和实现。而树状数组支持单点修改和快速求前缀和,于是用树状数组维护差分数组就能快速做到单点查询和区间修改。

支持区间修改区间查询的树状数组

  也许你认为线段树可以完成,但是对于这种水题不想出动线段树(懒得写),这时就可以考虑一下树状数组。

  树状数组可以优秀地利用差分来求单点,所以先考虑暴力用差分求前缀和(考虑差分数组每个位置被计算的次数)。

  

  这个还可以化简原式。然后发现这两个都可以用树状数组维护,然后对于任意区间减一减就好了。

Code

  1 /**
  2  * Codevs
  3  * Problem#1082
  4  * Accepted
  5  * Time:430ms
  6  * Memory:11240k
  7  */
  8 #include <iostream>
  9 #include <cstdio>
 10 #include <ctime>
 11 #include <cmath>
 12 #include <cctype>
 13 #include <cstring>
 14 #include <cstdlib>
 15 #include <fstream>
 16 #include <sstream>
 17 #include <algorithm>
 18 #include <map>
 19 #include <set>
 20 #include <stack>
 21 #include <queue>
 22 #include <vector>
 23 #include <stack>
 24 #ifndef WIN32
 25 #define Auto "%lld"
 26 #else
 27 #define Auto "%I64d"
 28 #endif
 29 using namespace std;
 30 typedef bool boolean;
 31 const signed int inf = (signed)((1u << 31) - 1);
 32 const signed long long llf = (signed long long)((1ull << 63) - 1);
 33 const double eps = 1e-6;
 34 const int binary_limit = 128;
 35 #define smin(a, b) a = min(a, b)
 36 #define smax(a, b) a = max(a, b)
 37 #define max3(a, b, c) max(a, max(b, c))
 38 #define min3(a, b, c) min(a, min(b, c))
 39 template<typename T>
 40 inline boolean readInteger(T& u){
 41     char x;
 42     int aFlag = 1;
 43     while(!isdigit((x = getchar())) && x != ‘-‘ && x != -1);
 44     if(x == -1) {
 45         ungetc(x, stdin);
 46         return false;
 47     }
 48     if(x == ‘-‘){
 49         x = getchar();
 50         aFlag = -1;
 51     }
 52     for(u = x - ‘0‘; isdigit((x = getchar())); u = (u * 10) + x - ‘0‘);
 53     ungetc(x, stdin);
 54     u *= aFlag;
 55     return true;
 56 }
 57
 58 #define LL long long
 59 #define lowbit(x) (x & (-x))
 60
 61 typedef class IndexedTree {
 62     public:
 63         LL* a;
 64         int s;
 65         IndexedTree():a(NULL), s(0) {        }
 66         IndexedTree(int n):s(n) {
 67             a = new LL[(n + 1)];
 68             memset(a, 0, sizeof(LL) * (n + 1));
 69         }
 70
 71         inline void add(int idx, LL val) {
 72             for(; idx <= s; idx += lowbit(idx))
 73                 a[idx] += val;
 74         }
 75
 76         inline LL getSum(int idx) {
 77             LL rt = 0;
 78             for(; idx; idx -= lowbit(idx))
 79                 rt += a[idx];
 80             return rt;
 81         }
 82 }IndexedTree;
 83
 84 typedef class SegmentableIndexedTree {
 85     public:
 86         LL *a;
 87         LL *ps;                    // prefix sum
 88         IndexedTree s;            // sum
 89         IndexedTree us;            // unique sum
 90
 91         SegmentableIndexedTree():a(NULL) {        }
 92         SegmentableIndexedTree(int n, LL *a):a(a) {
 93             s = IndexedTree(n + 1);
 94             us = IndexedTree(n + 1);
 95             ps = new LL[(n + 1)];
 96             ps[0] = 0;
 97             for(int i = 1; i <= n; i++)
 98                 ps[i] = ps[i - 1] + a[i];
 99         }
100
101         inline void add(int l, int r, LL val) {
102             s.add(l, val), us.add(l, l * val);
103             s.add(r + 1, -val), us.add(r + 1, -(r + 1) * val);
104         }
105
106         inline LL getSum(int idx) {
107             return (idx + 1) * s.getSum(idx) - us.getSum(idx);
108         }
109
110         inline LL getSum(int l, int r) {
111             return ps[r] - ps[l - 1] + getSum(r) - getSum(l - 1);
112         }
113 }SegmentableIndexedTree;
114
115 int n, m;
116 LL *a;
117 SegmentableIndexedTree sit;
118
119 inline void init() {
120     readInteger(n);
121     a = new LL[(n + 1)];
122     for(int i = 1; i <= n; i++)
123         readInteger(a[i]);
124 }
125
126 inline void solve() {
127     int opt, l, r, x;
128     sit = SegmentableIndexedTree(n, a);
129     readInteger(m);
130     while(m--) {
131         readInteger(opt);
132         readInteger(l);
133         readInteger(r);
134         if(opt == 1) {
135             readInteger(x);
136             sit.add(l, r, x);
137         } else {
138             printf(Auto"\n", sit.getSum(l, r));
139         }
140     }
141 }
142
143 int main() {
144     init();
145     solve();
146     return 0;
147 }
时间: 2024-10-06 17:00:29

回顾树状数组的相关文章

【转载】【树状数组区间第K大/小】

原帖:http://www.cnblogs.com/zgmf_x20a/archive/2008/11/15/1334109.html 回顾树状数组的定义,注意到有如下两条性质: 一,c[ans]=sum of A[ans-lowbit(ans)+1 ... ans];二,当ans=2^k时, c[ans]=sum of A[1 ... ans]; 下面说明findK(k)如何运作:1,设置边界条件ans,ans'<maxn且cnt<=k:2,初始化cnt=c[ans],其中ans=2^k且k

浅谈树状数组套主席树

话说主席树还没写就先写这一篇了\(qwq\) 回顾一下主席树的实现过程:类似查分思想,将线段树的每次修改看做函数式以支持可持久化.因为这样的线段树是可减的. 那么我们维护信息的时候,就要维护每一次新形成的信息.但是我们可以根据前一个信息的基础上进行改动,而不必要去再建一棵树. 所以总而言之,是前缀和的思想. 那么,当需要修改的时候,怎么做呢? 考虑普通的区间操作,当做单点修改的时候,一般用树状数组,线段树和分块.最好实现的就是树状数组. 考虑用树状数组来维护主席树的信息. 树状数组中维护了每一次

HDU 5542 The Battle of Chibi dp+树状数组

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5542 题意:给你n个数,求其中上升子序列长度为m的个数 可以考虑用dp[i][j]表示以a[i]结尾的长度为j的上升子序列有多少 裸的dp是o(n2m) 所以需要优化 我们可以发现dp的第3维是找比它小的数,那么就可以用树状数组来找 这样就可以降低复杂度 #include<iostream> #include<cstdio> #include<cstring> #include

(POJ 3067) Japan (慢慢熟悉的树状数组)

Japan Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 29295   Accepted: 7902 Description Japan plans to welcome the ACM ICPC World Finals and a lot of roads must be built for the venue. Japan is tall island with N cities on the East coas

【二维树状数组】See you~

https://www.bnuoj.com/v3/contest_show.php?cid=9148#problem/F [题意] 给定一个矩阵,每个格子的初始值为1.现在可以对矩阵有四种操作: A x y n1 :给格点(x,y)的值加n1 D x y n1: 给格点(x,y)的值减n1,如果现在格点的值不够n1,把格点置0 M x1 y1 x2 y2:(x1,y1)移动给(x2,y2)n1个 S x1 y1 x2 y2 查询子矩阵的和 [思路] 当然是二维树状数组 但是一定要注意:lowbi

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

CF 313 DIV2 B 树状数组

http://codeforces.com/contest/313/problem/B 题目大意 给一个区间,问你这个区间里面有几个连续相同的字符. 思路: 表示个人用树状数组来写的...了解了树状数组的本质就行了. 当然用sum[r]-sum[l]也是可以的

Hdu5032 极角排序+树状数组

题目链接 思路:参考了题解.对询问进行极角排序,然后用树状数组维护一下前缀和即可. /* ID: onlyazh1 LANG: C++ TASK: test */ #include<bits/stdc++.h> using namespace std; #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 typedef long long ll; const int maxn=1010; const int maxm=10

Curious Robin Hood(树状数组+线段树)

1112 - Curious Robin Hood    PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 64 MB Robin Hood likes to loot rich people since he helps the poor people with this money. Instead of keeping all the money together he does another tri