树状数组维护区间和的模型及其拓广的简单总结
树状数组及其应用

树状数组及其应用(Binary Indexed Trees)一、什么是树状数组【引例】假设有一列数{A i}(1<=i<=n),支持如下两种操作:1.将A k的值加D。
(k,D是输入的数)2.输出A s+A s+1+…+A t(s,t都是输入的数,s<=t)分析一:线段树建立一颗线段树(线段长度1~n)。
一开始所有结点的count值等于0。
对于操作1,如果把A k的值加D,则把所有覆盖了A k的线段的count值加D。
只有log2n 条线段会受到影响,因此时间复杂度是O(log2n)。
每条线段[x..y]的count值实际上就是A x+A x+1+…+A y的值。
对于操作2,实际上就是把[s..t]这条线段分解成为线段树中的结点线段,然后把所有的结点线段的count值相加。
该操作(ADD操作)在上一讲线段树中已介绍。
时间复杂度为O (log2n)。
分析二:树状数组树状数组是一种特殊的数据结构,这种数据结构的时空复杂度和线段树相似,但是它的系数要小得多。
增加数组C,其中C[i]=a[i-2^k+1]+……+a[i](k为i在二进制形式下末尾0的个数)。
由c数组的定义可以得出:i K1(1)201-2^0+1=1…1c[1]=a[1]2(10)212-2^1+1=1…2c[2]=a[1]+a[2]=c[1]+a[2]3(11)203-2^0+1=3…3c[3]=a[3]4(100)224-2^2+1=1…4c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4] 5(101)205-2^0+1=5…5c[5]=a[5]6(110)216-2^1+1=5…6c[6]=a[5]+a[6]=c[5]+a[6]………………为了对树状数组有个形象的认识,我们先看下面这张图。
如图所示,红色矩形表示的数组C[]就是树状数组。
我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
树状数组的操作

树状数组的操作树状数组的⼀些基本操作。
树状数组⽀持单点修改和查询区间和的操作,但和线段树不同,它不⽀持区间修改操作(有些题⽬可以将区间修改转化为单点修改,有些则不可以)。
下⾯介绍树状数组的预处理和基本操作。
1.求lowbit(n)上⼀篇博客介绍了lowbit的定义和使⽤定义的基本求法。
但是依据定义求lowbit未免⿇烦。
因⽽,求lowbit需要导⼊公式:lowbit(n)=n&(-n)。
求lowbit函数:int lowbit(int n){//求n的lowbitreturn n&(-n);}2.对某个元素进⾏加法操作根据树状数组的性质,对于任意⼀个结点x,只有结点c[x]及其祖先结点保存的区间和中含a[x],所以我们只需要对x不断向上搜索即可。
代码:void update(int x,int y){//将x位置元素加上yfor(;x<=N;x+=lowbit(x){//依次寻找x的⽗节点c[x]+=y;//进⾏更新}}这⾥在寻找x的⽗节点时运⽤了上⼀篇博客中的性质③(具体的证明⽅法我也不会qwq)。
举例证明:假设我们要对A[2]进⾏加x的操作。
2的lowbit值为2,则第⼀步2变成了4,对C[4]进⾏处理。
4的lowbit值为4,则第⼆步变成了8,对C[8]进⾏处理。
此时已到达循环边界。
再假设我们要对A[5]进⾏加x的操作。
5的lowbit值为1,则第⼀步5变成了6,对C[6]进⾏处理。
6的lowbit值为2,则第⼆步变成了8,对C[8]进⾏处理。
此时已到达循环边界。
3.查询前缀和操作前缀和的求法:求出x的⼆进制表⽰中每个等于1的位,把[1,x]分成logN个⼩区间,⽽每个⼩区间的区间和已保存在数组c中。
代码(我也不会证):int sum(int x){//求1~x的区间和int ans=0;for(;x;x-=lowbit(x);){ans+=c[x];}return ans;}举例证明:假设我们要求A[6]的前缀和。
树状数组离线技巧

树状数组离线技巧(原创实用版3篇)目录(篇1)1.树状数组的基本概念2.树状数组的离线算法3.离线算法的优化4.离线算法的应用正文(篇1)树状数组是一种基于数组的统计数据方法,可以快速计算多个等差数列的和。
其基本概念包括数组、计数器和递减性。
离线算法是树状数组的一种优化算法,可以提高计算效率。
离线算法的核心思想是利用二进制表示和压缩存储来减少空间占用。
1.树状数组的基本概念树状数组是一种基于数组的统计数据方法,可以快速计算多个等差数列的和。
其基本概念包括数组、计数器和递减性。
树状数组可以通过在数组中插入元素来计算多个等差数列的和,同时还可以维护计数器和递减性。
2.树状数组的离线算法离线算法是树状数组的一种优化算法,可以提高计算效率。
离线算法的核心思想是利用二进制表示和压缩存储来减少空间占用。
具体来说,离线算法将树状数组中的每个节点都表示为一个二进制数,并将计数器压缩存储为一个数组。
这样,就可以减少空间占用,提高计算效率。
3.离线算法的优化离线算法的优化包括利用二进制表示和压缩存储来减少空间占用,以及利用计数器的稀疏性来减少计算量。
二进制表示可以将树状数组中的每个节点都表示为一个二进制数,从而减少空间占用。
压缩存储可以将计数器压缩存储为一个数组,从而减少空间占用。
计数器的稀疏性是指树状数组中大多数节点的计数器值较小,可以利用这个特性来减少计算量。
4.离线算法的应用离线算法可以应用于很多实际问题中,比如搜索和查询、快速加法和去重等。
离线算法可以利用二进制表示和压缩存储来减少空间占用,从而提高计算效率。
目录(篇2)1.树状数组的基本概念2.树状数组的离线算法3.离线算法的优化4.离线算法的应用正文(篇2)树状数组是一种基于数组的统计数据集的方法,可以快速计算给定区间的和。
树状数组的主要优点是可以有效地减少计算量,提高计算速度。
下面我们来介绍树状数组的离线算法及其优化和应用。
一、树状数组的基本概念树状数组是一种基于数组的统计数据集的方法,可以快速计算给定区间的和。
树状数组

2 区间更新,单点求和
举个具体的栗子。。。
这道题目就是
意思挺明显的。。。 就是更改,查询。。。。
再举个特别的栗子。。。
求逆序数~
例如在序列 { 2, 4, 3, 1 } 中,逆序依次为 (2,1), (4,3), (4,1), (3,1),因此该序列的逆序数为 4。
所以如何求一个数组中逆序数的个数呢。。
//而且是用树状数组的方法。。。
例如 1 5 2 4 3
从输入顺序考虑: 输入1 ,无逆序;
输入 5, 无逆序;
输入 2,有逆序,因为前面有个5,有一个逆序对,这个逆序 对也可以这样考虑,此刻2所在的位置为3,而此刻小于等于2 的数有2 个,所以3-2=1; 输入 4,有逆序,同上,4-3=1; 输入 3 ,有逆序,同上,5-3=2; 所以~ ans=1+1+2~~~
3 树状数组的两类操作 1 单点更新,区间求和 1 一维树状数组,单点更新,区间求和 比如要更新点x ,x点的值加上val即调用add(x , val) , 求区间[1 , x]的和即为getSum(x) int lowbit(int x) { return x&(-x); } int getSum(int x){ int sum = 0; while(x){ sum += treeNum[x]; x -= lowbit(x); } return sum; } void add(int x , int val){ while(x <=N){ treeNum[x] += val; x += lowbit(x); } }
问题的提出
有一个一维数组,长度为n。
史上最易懂的树状数组入门教程共21页文档

3.每个ei包含的元素数目(包括ai在内)为i的二进制表示中末位连续的0的个 数
为什么这样安排呢? 由主讲人来分析一下它的效率
1.对应二进制数的最末连续0的个数如何 得知?
2.如何存储?
3.如何在修改时访问需要访问的元素?
4.如何在求和时访问需要访问的元素?
如何实现
位运算!
之后将会说到,在实际了应解更用多中位,运我算们,补需码相要关的不 是最末连续的0的个信数息,而,参是见<最位末运算那入段门”1000” 对应的十进制数(虽然Au二le秘者密显增然补版可>以互推)
时间复杂度:单次修改O(1),单次求和最坏 O(n),总时间复杂度最坏为O(n^2),会超时.
为什么超时呢?因为每次我求和的速度太慢 了,那么我们想到了一个被称为“容斥原理” 的小技巧对求和进行加速,再看看.
方法2:开一个1..100000的数组e,e[i]存储的 是a1+a2+..+ai
要求ai..aj的和,那么就是ej-e(i-1). 显然!这下,单次求和的复杂度就变成了O(1)!
当我们求的信息时如果包含的不是的全部信息比如就需要再 找一个显然累加起来这个我们称之为的前驱举个例子
前驱的编号即为比自己小的最近的最末连续比自己多的数 所以前驱=x-lowbit(x),详情参见主讲人
至此我们的树状数组就把大意讲解完毕 了~
可能你会问,如何建立树状数组?
模 就板当作树状数组一开始都是空的,不停的
存储e数组即可,无需存储a数组.
1.对应二进制数的最末连续0的个数如何 得知?
2.如何存储?
3.如何在修改时访问需要访问的元素?
4.如何在求和时访问需要访问的元素?
如何实现
树状数组线段树简单介绍

线段存数!
线段树的基本操作
建树:
1、由于线段树的特殊性,可以使用类似完全二 叉树的结构来建立,但是线段树不是完全二叉 树,开4倍的空间才可以保证结构正确性! 2、可以使用动态申请空间方法new,delete 3、与开一个数组空间,利用游标控制建立线段 树,同时记录左右儿子的下标。
线段树的基本操作
二维树状数组的简单介绍
对于一个二维数组a[][]以及其对应的二维c[][] {1,1,1,1} {1,2,1,4} {1,1,1,1} {2,4,2,8} {1,1,1,1} {1,2,1,4} {1,1,1,1} {4,8,4,16} 思想很简单,具体操作我的blog上有,有时间大 家可以去看看。
[0,8] [0,4] [0,2]
[6,8]
[4,8] [2,4] [4,6] [6,8]
[4,8] [0,8] [0,4]
[0,2] [2,4]
[4,6]
二维线段树的结构介绍
二维线段树的第二种形式:
(4,3)
(1,1)
(2,3) (1,2) (2,2)
(4,3) (1,1)
(2,2)
(4,2)
线段树的基本操作
计算连续段数
连续线段数line指的是区间中互不相交的线段条数。
连续线段数并不能像测度那样将两个子结点中的连续线段数简单 相加。于是我们引进了两个量lbd,rbd,分别表示区间的左右两 端是否被线段覆盖。 1 (左端点被线段覆盖到) lbd = 0 (左端点不被线段覆盖到) 1 (右端点被线段覆盖到) rbd = 0 (右端点不被线段覆盖到)
线段树例题分析
例5(poj 3277 City Horizon) 题目描述:已知N座建筑,每座建筑对于一个三 元组(Ai,Bi,Hi)表示从Ai到Bi高为Hi的建筑, 求从侧面看可看到的总面积。 Sample input Sample output
树状数组延伸和离线优化(CDQ、整体二分和莫队)-myt
树状数组延伸和离线优化(CDQ、整体二分和莫队)数据结构离线优化树状数组1、一般树状数组1.int lowbit(int x){2. return x & (-x);3.}4.long long sum(long long a[],int end){5. long long res=0;6. while(end){7. res=res+a[end];8. end=end-lowbit(end);9. }10. return res;11.}12.void modify(long long a[],int p,int d){13. while(p<=n){14. a[p]+=d;15. p=p+lowbit(p);16. }17.}一般树状数组的特点:单点更新,区间查询。
2、区间更新例子:POJ 3468题意:给出N个数,M个操作,操作分为2种:查询[l,r]的和;给[l,r]中每个数加上d。
N,M<=10^5;线段树当然可以做。
但是线段树占用的空间和时间都是树状数组的常数倍,所以还是优先使用树状数组。
如何修改原有的树状数组是的它能使用区间更新呢。
STEP1:更新操作:把[l,r]所有的数加上d,可以看做把[l,n]所有数加上d,再把[r+1,n]所有数减去d。
那我们引入一个新的数组delta[n],delta[i]记录了[i,n]每个数的增量。
操作就转化为了delta[l]+=d,delta[r+1]-=d;SETP2:查询操作:求[l,r]的和,当然是看做求sum[1,r]-sum[1,l-1]啦。
就是求sum(x)。
首先,要加上原数组的基数前缀和origin[x],这个一开始就能求出来。
然后,考虑delta数组,delta[1]为[1,x]贡献了x个delta[1],delta[2]为[2,x]贡献了x-1个delta[2],以此类推,delta[i]贡献了(x+1-i)个delta[i]。
c++逆序对个数 树状数组
逆序对个数和树状数组是两个不同的概念,但它们在某些算法中可以一起使用。
1. 逆序对个数:逆序对是数组中两个元素,第一个元素大于第二个元素。
在C++中,可以使用STL中的`std::sort`函数来对数组进行排序,然后使用双指针法来计算逆序对的个数。
具体实现如下:```cpp#include <iostream>#include <algorithm>using namespace std;int reversePairs(int* nums, int numsSize) {sort(nums, nums + numsSize);int count = 0, i = 0, j = numsSize - 1;while (i < j) {if (nums[i] > nums[j]) {count += j - i;i++;} else {j--;}}return count;}```2. 树状数组:树状数组是一种可以高效地维护和更新前缀和的数据结构,也称为 Fenwick 树。
在C++中,可以使用一个数组和一个辅助数组来实现树状数组。
具体实现如下:```cpp#include <iostream>#include <vector>using namespace std;class FenwickTree {public:FenwickTree(int n) {tree.resize(n + 1);prefixSum.resize(n + 1);}void update(int index, int delta) {while (index <= tree.size() - 1) {tree[index] += delta;prefixSum[index] += delta;index += index & -index;}}int query(int index) {int sum = 0;while (index > 0) {sum += prefixSum[index];index -= index & -index;}return sum;}private:vector<int> tree; // Fenwick tree arrayvector<int> prefixSum; // prefix sum array for Fenwick tree array}; ```。
二维树状数组详解
⼆维树状数组详解%%⼤连市理科状元。
“⾼级”数据结构——树状数组!※本⽂⼀切代码未经编译,不保证正确性,如发现问题,欢迎指正!1. 单点修改 + 区间查询最简单的树状数组就是这样的:void add(int p, int x){ //给位置p增加xwhile(p <= n) sum[p] += x, p += p & -p;}int ask(int p){ //求位置p的前缀和int res = 0;while(p) res += sum[p], p -= p & -p;return res;}int range_ask(int l, int r){ //区间求和return ask(r) - ask(l - 1);}2. 区间修改 + 单点查询通过“差分”(就是记录数组中每个元素与前⼀个元素的差),可以把这个问题转化为问题1。
查询设原数组为a[i]修改当给区间[l,r]void add(int p, int x){ //这个函数⽤来在树状数组中直接修改while(p <= n) sum[p] += x, p += p & -p;}void range_add(int l, int r, int x){ //给区间[l, r]加上xadd(l, x), add(r + 1, -x);}int ask(int p){ //单点查询int res = 0;while(p) res += sum[p], p -= p & -p;return res;}3. 区间修改 + 区间查询这是最常⽤的部分,也是⽤线段树写着最⿇烦的部分——但是现在我们有了树状数组!怎么求呢?我们基于问题2的“差分”思路,考虑⼀下如何在问题2构建的树状数组中求前缀和:位置p的前缀和 =∑i=1pa[i]=∑i=1p∑j=1id[j]在等式最右侧的式⼦∑pi=1∑ij=1d[j]位置p的前缀和 =∑i=1p∑j=1id[j]=∑i=1pd[i]∗(p−i+1)=(p+1)∗∑i=1pd[i]−∑i=1pd[i]∗i那么我们可以维护两个数组的前缀和:⼀个数组是sum1[i]=d[i]查询位置p的前缀和即: (p + 1) * sum1数组中p的前缀和 - sum2数组中p的前缀和。
『树状数组』树状数组模板
『树状数组』树状数组模板『树状数组』树状数组模板竞赛需要⽤到的点树状数组可以解决⼤部分基于区间上更新、查询的操作树状数组能写的线段树都能写,但线段树能写的树状数组不⼀定能写代码量⼩,且常数⽐线段树⼩树状数组是树与⼆进制的混合使⽤lowbit(x) -> x&(-x) 为何? -x = x的⼆进制数中 0 变 1,1 变 0 然后末尾 + 1 lowbit可以得到⼀个由原⼆进制数变来的只有末尾1的新的⼆进制数树状数组略讲此处借⽤的图⽚由此图我们可以得到以下信息(所有信息均⽤⼆进制处理)所有区间所包含的点的数量为 2k其中k为该数⼆进制下末尾 0 的个数[单点修改] 当我们修改⼀个点i的值后,那么所包含i的区间都要改变哪些值要变?i在⼆进制下,加上该⼆进制下最低位1 例如(1001 -> 1010)(9 -> 10)[单点查询] 因为树状数组存储的形式类似于前缀和的形式,所以单点查询的办法也显⽽易见如何查询?查询b i与b i−1的值,然后两式相减可得a i[区间查询] 和单点查询类似若查询[i,j] ,那么可得b j−b i−1[区间更新] 对于这种问题我们分两种情况[区间修改&单点查询] 这⾥我们需要⽤到差分的思想,即将原本的树状数组的数组⽤差分数组来表⽰b i=a i−a i−1,答案也呼之欲出,对于差分我们每次想得到a n都是Σn i=1b i然后对于每次修改,我们对b i与b j+1 进⾏单点修改即可。
[区间修改&区间查询] 这⾥我们也要⽤到差分的思想,但这次我们需要维护两个差分数组(sum1 和sum2)为什么?我们从上⾯可以得到,想要知道a[i,j]的和就得⽤以下式⼦求出,Σn i=1a=Σn i=1Σi j=1b j激动⼈⼼的化简时刻到了(−b i)&=nΣn i=1b i+Σn i=2(−b i)×(i−1)&=nΣn i=1b i−Σn i=2b i×(i−1)Σn i=1a=Σn i=1Σi j=1b j&=n×b1+(n−1)×b2+(n−2)×b3+...+b n&=nΣn i=1b i−b2−2b3−3b4−...−(n−1)b n&=nΣn i=1b i+Σn i=2Σi−1j=1因为Σn i=2b i×(i−1) 与Σn i=1b i×(i−1) 等价所以原式⼦ = nΣn i=1b i−Σn i=1b i×(i−1) 在这个式⼦中,由于有Σn i=1所以我们可以易得⽤差分,⽤两个差分数组维护b i与b i×(i−1) 就可以了部分模板代码展现单点修改单点查询#define fre yes#include <cstdio>const int N = 100005;int a[N], b[N];int n;int lowbit(int x) {return x & (-x);}void point_change(int x, int k) { // point_change(x, k)while(x <= n) {b[x] += k;x += lowbit(x);}}int getSum(int x) { // getSum(x) - getSum(x - 1)int res = 0;while(x > 0) {res += b[x];x -= lowbit(x);} return res;}int main() {...}单点修改区间查询#define fre yes#include <cstdio>const int N = 100005;int a[N], b[N];int n;int lowbit(int x) {return x & (-x);}void point_change(int x, int k) { // point_change(x, k)while(x <= n) {b[x] += k;x += lowbit(x);}}int getSum(int x) { // getSum(r) - getSum(l - 1)int res = 0;while(x > 0) {res += b[x];x -= lowbit(x);} return res;}int main() {...}区间修改单点查询#define fre yes#include <cstdio>const int N = 100005;int a[N], b[N];int n;int lowbit(int x) {return x & (-x);}void interval_change(int x, int k) { // point_change(l, x) point_change(r + 1, -x) while(x <= n) {b[x] += k;x += lowbit(x);}}int getSum(int x) { // getSum(x)int res = 0;while(x > 0) {res += b[x];x -= lowbit(x);} return res;}int main() {...}区间查询区间修改#define fre yes#include <cstdio>const int N = 100005;int sum1[N], sum2[N];int n;int lowbit(int x) {return x & (-x);}void interval_change(int j, int k) {// push -> interval_change(i, a[i] - a[i - 1])// change -> interval_change(l, k) interval_change(r + 1, -k)int i = j;while(j <= n) {sum1[j] += k;sum2[j] += k * (i - 1);j += lowbit(j);}}int getSum(int i) { // getSum(r) - getSum(l - 1)int res = 0, x = i;while(i > 0) {res += x * sum1[i] - sum2[i];i -= lowbit(i);} return res;}int main() {...}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
树状数组维护区间和的模型及其拓广的简单总结 by wyl8899 树状数组的基本知识已经被讲到烂了,我就不多说了,下面直接给出基本操作的代码。 假定原数组为a[1..n],树状数组b[1..n],考虑灵活性的需要,代码使用int *a传数组。 #define lowbit(x) ((x)&(-(x))) int sum(int *a,int x){ int s=0; for(;x;x-=lowbit(x))s+=a[x]; return s; } void update(int *a,int x,int w){ for(;x<=n;x+=lowbit(x))a[x]+=w; } sum(x)返回原数组[1,x]的区间和,update(x,w)将原数组下标为x的数加上w。 这两个函数使用O(操作数*logn)的时间和O(n)的空间完成单点加减,区间求和的功能。
接下来做一些升级,让树状数组完成区间加减,单点查询的功能。 直接做的话很困难,需要对问题做一些转化。 考虑将原数组差分,即令d[i]=a[i]-a[i-1],特别地,d[1]=a[1]。 此时a[i]=d[1]+..+d[i],所以单点查询a[i]实际上就是在求d数组的[1..i]区间和。 而区间[l,r]整体加上k的操作,可以简单地使用d[l]+=k和d[r+1]-=k来完成。 于是,我们用树状数组来维护d[],就可以解决问题了。
下面再升级一次,完成区间加减,区间求和的功能。 仍然沿用d数组,考虑a数组[1,x]区间和的计算。d[1]被累加了x次,d[2]被累加了x-1次,...,d[x]被累加了1次。 因此得到 sigma(a[i]) =sigma{d[i]*(x-i+1)} =sigma{ d[i]*(x+1) - d[i]*i } =(x+1)*sigma(d[i])-sigma(d[i]*i) 所以我们再用树状数组维护一个数组d2[i]=d[i]*i,即可完成任务。 POJ 3468就是这个功能的裸题,下面给出代码。 [请注意我们上面的讨论都假定了a[]初始全是0。如果不是这样呢?下面的程序里给出了一个相对简便的处理办法。] // POJ 3468 Using BIT
#include const int maxn=100010; __int64 a[maxn],b[maxn],c[maxn]; int n,m; inline int lowbit(const int &x){ return x&(-x); }
__int64 query(__int64 *a,int x){ __int64 sum=0; while(x){sum+=a[x];x-=lowbit(x);} return sum; }
void update(__int64 *a,int x,__int64 w){ while(x<=n){a[x]+=w;x+=lowbit(x);} }
int main(){ int l,r,i; __int64 ans,w; char ch; scanf("%d%d",&n,&m); a[0]=0; for(i=1;i<=n;++i){ scanf("%I64d",&a[i]); a[i]+=a[i-1]; } while(m--){ scanf("%c",&ch); while(ch!='Q' && ch!='C')scanf("%c",&ch); if(ch=='Q'){ scanf("%d%d",&l,&r); ans=a[r]-a[l-1]+(r+1)*query(b,r)-l*query(b,l-1)-query(c,r)+query(c,l-1); printf("%I64d\n",ans); }else{ scanf("%d%d%I64d",&l,&r,&w); update(b,l,w); update(b,r+1,-w); update(c,l,w*l); update(c,r+1,-(r+1)*w); } } return 0; } [当a[]初始不全0的时候,我们就只维护后来加上去的部分,查询区间和的时候再补上初始的时候这一段的区间和就可以了。] ======================一维到二维的分割线========================= 接下来到二维树状数组。 先看看sum和update变成什么样子了吧。 inline int gs(int a[maxn][maxn],int x,int y){ int s=0,t; for(;x;x-=lowbit(x)) for(t=y;t;t-=lowbit(t)) s+=a[x][t]; return s; } inline void gp(int a[maxn][maxn],int x,int y,int w){ int t; for(;x<=n;x+=lowbit(x)) for(t=y;t<=m;t+=lowbit(t)) a[x][t]+=w; } gs就是sum,gp就是update,由于需要多次调用的缘故,改成了更短的名字。 单点加减,矩形求和并不难,直接用上面的两段就行了。 需要注意的是矩形的求和怎么求。上面的代码返回的是(1,1)-(x,y)矩形的和。 那么(x1,y1)-(x2,y2)的矩形和由下式给出: sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1) 画个图就很好理解了。
对于涉及矩形加减的情形,我们发现一维中的差分的办法在二维的情况用不出来,所以要改一下。思考一下一维中的差分的另外一个含义:d[i]同时也表示d[i..n]的整体增量,d[i]+=k就意味着把d[i]..d[n]全部加上了k。理解了之后就发现这个意义上可以推广到二维,仍假设原矩形初始全为0,以便接下来的叙述。 令a[x,y]表示(x,y)-(n,m)矩形的整体增量,其中(n,m)是边界。 那么(x1,y1)-(x2,y2)矩形整体加k的代码就是 gp(a,x1,y1,w); gp(a,x2+1,y1,-w);gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w); 仍然是建议画个图来帮助理解。
至此,矩形加减,单点查询的问题得到了解决。 重头戏在这里,矩形加减,矩形求和。 求原矩形(1,1)-(x,y)的和,结果由下式给出 sigma(i=1..x,j=1..y) a[i,j]*(x-i+1)*(y-j+1) 很好理解吧? 但是这个式子并不是那么容易求和的,展开一下求和的部分得到 a[i,j]* ( (x+1)(y+1) - (x+1)*j - (y+1)*x + i*j ) 整个式子就是 (x+1)(y+1)sigma(a[i,j]) - (x+1)sigma(a[i,j]*j) - (y+1)sigma(a[i,j]*i) + sigma(a[i,j]*i*j) 知道怎么处理了吧?如果没有请回去复习一维的处理方法。 令b[i,j]=a[i,j]*i c[i,j]=a[i,j]*j d[i,j]=a[i,j]*i*j 维护a,b,c,d一共四个二维树状数组,问题得到解决。 tyvj p1716就是实现这两个功能的裸题,下面给出完整代码。
// tyvj p1716 using 2D BIT #include #include #define lowbit(x) ((x)&(-(x))) const int maxn=2049; int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],d[maxn][maxn]; int n,m;
inline int gs(int a[maxn][maxn],int x,int y){ int s=0,t; for(;x;x-=lowbit(x)) for(t=y;t;t-=lowbit(t)) s+=a[x][t]; return s; } inline void gp(int a[maxn][maxn],int x,int y,int w){ int t; for(;x<=n;x+=lowbit(x)) for(t=y;t<=m;t+=lowbit(t)) a[x][t]+=w; } inline int sum(int x,int y){ return (x+1)*(y+1)*gs(a,x,y)-(y+1)*gs(b,x,y)-(x+1)*gs(c,x,y)+gs(d,x,y); } inline void update(int x1,int y1,int x2,int y2,int w){ gp(a,x1,y1,w); gp(a,x2+1,y1,-w); gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w); gp(b,x1,y1,w*x1); gp(b,x2+1,y1,-w*(x2+1)); gp(b,x1,y2+1,-w*x1); gp(b,x2+1,y2+1,w*(x2+1)); gp(c,x1,y1,w*y1); gp(c,x2+1,y1,-w*y1); gp(c,x1,y2+1,-w*(y2+1)); gp(c,x2+1,y2+1,w*(y2+1)); gp(d,x1,y1,w*x1*y1); gp(d,x2+1,y1,-w*(x2+1)*y1); gp(d,x1,y2+1,-w*x1*(y2+1)); gp(d,x2+1,y2+1,w*(x2+1)*(y2+1)); }
int main(){ int x1,y1,x2,y2,w; char ch; scanf("%c",&ch); while(ch!='X')scanf("%c",&ch); scanf("%d%d\n",&n,&m);