树状数组求区间和的一些常见模型

合集下载

树状数组区间求和

树状数组区间求和

树状数组区间求和树状数组是一种用于高效求解区间和的数据结构。

它可以在O(log n)的时间复杂度内完成单点更新和区间求和操作,比传统的数组操作要快得多。

本文将介绍树状数组的原理、实现以及应用场景。

一、树状数组的原理树状数组的原理基于二进制的思想。

假设有一个长度为n的数组a,我们可以将其转化为一个长度为n的树状数组c。

树状数组的每个节点存储了一段原数组的区间和。

树状数组中的每个节点的编号i 代表的是原数组中的第1个到第i个元素的区间和。

树状数组的构建过程如下:1. 初始化树状数组c,将其所有元素置为0。

2. 对于原数组a的每个元素a[i],更新树状数组c的节点c[i],使其加上a[i]。

3. 根据树状数组的性质,每个节点c[i]存储的是原数组a中从i 到i-lowbit(i)+1的区间和。

二、树状数组的实现树状数组的实现需要定义三个关键操作:求和操作、单点更新操作和区间求和操作。

1. 求和操作:从树状数组的根节点开始,不断向左子节点或者右子节点移动,直到达到叶子节点。

叶子节点存储的就是所求区间的和。

2. 单点更新操作:从待更新的节点开始,不断向根节点移动,每次移动时更新当前节点的值。

这样可以保持树状数组的正确性。

3. 区间求和操作:分别求出右边界的区间和和左边界-1的区间和,然后相减,即可得到所求区间的和。

三、树状数组的应用场景树状数组的高效性使得它在很多场景下被广泛应用。

1. 区间求和:树状数组可以高效地求解数组中任意区间的和,比如求解某个区间内的元素和、计算前缀和等。

2. 单点更新:树状数组可以高效地对单个元素进行更新,比如修改某个元素的值或者增加某个元素的数量。

3. 逆序对个数:树状数组可以高效地统计给定数组中逆序对的个数,即后面的元素比前面的元素大的情况。

4. 数组去重:树状数组可以高效地对一个数组进行去重操作,找出数组中的不重复元素。

5. 数组排序:树状数组可以高效地对一个数组进行排序,将数组中的元素按照某个规则重新排列。

用树状数组解决区间查询问题

用树状数组解决区间查询问题

用树状数组解决区间查询问题分享自斯里猫: 本文扩写自郭神的《树状数组新应用》,在此表示膜拜。

树状数组的学名貌似叫做Binary Index Tree,关于它的基本应用可参考Topcoder上的这篇Tutorial.树状数组可以看作一个受限制的线段树,它维护一个数组,最经典的树状数组支持的基本操作有两个:(1)改变某一个元素的值(2)查询某一个区间内所有元素的和。

在此基础上,经过简单的变形可以变成支持另一组操作:(1)把一个区间内所有元素都加上一个值(2)查询某一个元素的值。

这两个都是已经泛滥了的东西了,在此不赘述。

简单的树状数组模型是不支持这样一组操作的:(1)把某一个区间内所有元素都加上一个值(2)查询某一个区间内所有元素的和。

当然,这个东西可以用线段树完成,但是线段树占内存比较大,写起来也比较繁(对我这种不会数据结构的人而言)。

下面我们用一个改进版的树状数组完成这个任务。

首先一个观察是区间操作总可以变成从最左端开始,比如把区间[3..6]都加10,可以变成[1..6]加10, [1..2]减10。

查询也类似。

于是下面只关心从最左端开始的情况。

定义Insert(p, d)表示把区间[1..p]都加d,Query(p)表示查询区间[1..p]之和。

我们考虑调用一次Insert(p, d)对以后的某次查询Query(q)的影响:(1) 如果p<=q,总的结果会加上p*d (2) 如果p>q,总的结果会加上q*d也就是说,Query(q)的结果来源可分为两部分,一部分是Insert(p1,d) (p1<=q),一部分是Insert(p2,d) (p2 > q)。

我们用两个数组B[], C[]分别维护这两部分信息,B[i]表示区间右端点恰好是i的所有区间的影响之和,C[i]表示区间右端点大于i的所有区间的影响之和。

每当遇到Insert时,考虑当前的Insert会对以后的Query产生什么影响,更新B和C数组;当遇到Query时,把两部分的结果累加起来。

树状数组的操作

树状数组的操作

树状数组的操作树状数组的⼀些基本操作。

树状数组⽀持单点修改和查询区间和的操作,但和线段树不同,它不⽀持区间修改操作(有些题⽬可以将区间修改转化为单点修改,有些则不可以)。

下⾯介绍树状数组的预处理和基本操作。

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]的前缀和。

树状数组详解

树状数组详解

第01讲什么是树状数组?树状数组用来求区间元素和,求一次区间元素和的时间效率为O(logn)。

有些同学会觉得很奇怪。

用一个数组S[i]保存序列A[]的前i个元素和,那么求区间i,j的元素和不就为S[j]-S[i-1],那么时间效率为O(1),岂不是更快?但是,如果题目的A[]会改变呢?例如:我们来定义下列问题:我们有n个盒子。

可能的操作为1.向盒子k添加石块2.查询从盒子i到盒子j总的石块数自然的解法带有对操作1为O(1)而对操作2为O(n)的时间复杂度。

但是用树状数组,对操作1和2的时间复杂度都为O(logn)。

第02讲图解树状数组C[]现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]网络上面都有这个图,但是我将这个图做了2点改进。

(1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。

(2)C[i]为A[i]对应的那一列的最高的节点。

现在告诉你:序列C[]就是树状数组。

那么C[]如何求得?C[1]=A[1];C[2]=A[1]+A[2];C[3]=A[3];C[4]=A[1]+A[2]+A[3]+A[4];C[5]=A[5];C[6]=A[5]+A[6];C[7]=A[7];C[8]= A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];以上只是枚举了所有的情况,那么推广到一般情况,得到一个C[i]的抽象定义:因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C[i]=C[i]的所有叶子的和。

现在不得不引出关于二进制的一个规律:先仔细看下图:将十进制化成二进制,然后观察这些二进制数最右边1的位置:1 --> 000000012 --> 000000103 --> 000000114 --> 000001005 --> 000001016 --> 000001107 --> 000001118 --> 000010001的位置其实从我画的满二叉树中就可以看出来。

利用树状数组解决几类问题

利用树状数组解决几类问题

利用树状数组解决几类问题树状数组作为一种实现简单、应用较广的高级数据结构,在OI 界的地位越来越重要,下面我来简单介绍一下树状数组和它的简单应用。

一、树状数组简介树状数组(Binary Indexed Trees ,简称BIT)是一种特殊的数据结构,这种数据结构的时空复杂度和线段树相似,但是它的系数要小得多。

它可以方便地查询出一段区间中的数字之和。

其查询和修改的时间复杂度均为O(lbN),并且是一个在线的数据结构,可以随时修改并查询。

我接下来用一道题目来介绍树状数组的几个基本操作。

【引例】假设有一列数{A i }(1<=i<=n ),支持如下两种操作: 1. 将A k 的值加D 。

(k ,D 是输入的数)2. 输出∑=tsi i A (s ,t 都是输入的数,s<=t )这道题目用线段树很容易可以解决,我就不多说了,那么如何用树状数组来解决呢?我们新增一个数组C[],其中C[i]=a[i-2k+1]+……+a[i](k 为i 在二进制形式下末尾0的个数)。

低位技术,也叫Lowbit(k)。

对于Lowbit 这里我提三种不同的写法(这三种形式是等价的,读者有兴趣可以去证明一下):1.Lowbit(k)=k and (k or (k-1))2.Lowbit(k)=k and not (k-1)3.Lowbit(x)=k and –k然后我来分析引例的树状数组解法,为了可以更好地理解这种方法,读者可以根据以下这幅图来加以理解。

【操作2】修改操作:将A k 的值加D可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(lbN)。

定理1:若A[k]所牵动的序列为C[p 1],C[p 2]……C[p m ], 则p 1=k ,而ili i p p 21+=+(l i 为p i 在二进制中末尾0的个数)。

例如A[1]……A[8]中,a[3] 添加x ; p 1=k=3 p 2=3+20=4 p 3=4+22=8 p 4=8+23=16>8由此得出,c[3]、c[4]、c[8]亦应该添加x 。

树状数组的模板题

树状数组的模板题

树状数组的模板题【如果你不知道什么是树状数组:这是⼀道模板题。

给定数列a1,a2,…,a n,你需要进⾏m各操作,操作有两类:1 i x:给定i,x,将a i加上x;2 l r:给定l.r,求Σr i=l a i的值(换⾔之,求a l+a l+1+⋯+a r的值)。

输⼊格式第⼀⾏包含2个正整数n,m,表⽰数列长度和询问个数。

保证1≤n,m≤106。

第⼆⾏n个整数a1,a2,…,a n,表⽰初始数列。

保证|a i|≤106。

接下来m⾏,每⾏⼀个操作,为以下两种之⼀:1 i x:给定i,x,将a i加上x;2 l r:给定l.r,求Σr i=l a i的值。

保证1≤l≤r≤n,|x|≤106。

输出格式对于每个2 l r操作输出⼀⾏,每⾏有⼀个整数,表⽰所求的结果。

思路:这是⼀道简单的⼀维树状数组的模板题#include <bits/stdc++.h>using namespace std;typedef long long ll;#define endl '\n'#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);#define _for(i, a, b) for (int i=(a); i<=(b); i++)const int INF = 0x7fffffff;const int MAXN = 1e6 + 10;ll tree[MAXN], n, m;inline ll lowbit(ll x){return x & (-x);}void updata(ll x, ll d){while(x <= n){tree[x] += d;x += lowbit(x);}}ll getsum(ll x){ll res = 0;while(x > 0){res += tree[x];x -= lowbit(x);}return res;}int main(){IOScin >> n >> m;_for(i, 1, n){ll a;cin >> a;updata(i,a);}while(m--){ll id, x, y;cin >> id >> x >> y;if(id == 1){updata(x,y);}else if(id == 2){cout << getsum(y) - getsum(x-1) << endl;}}return 0;}这是⼀道模板题。

树状数组

树状数组

树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。

来观察这个图:令这棵树的结点编号为C1,。

令每个结点的值为这棵树的值的总和,那么容易发现:C1 = A1C2 = A1 + A2C3 = A3 C4 = A1 + A2 + A3 + A4C5 = A5C6 = A5 + A6C7 = A7C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8...C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16这里有一个有趣的性质:设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。

因为这个区间最后一个元素必然为Ax,所以很明显:Cn = A(n –2^k + 1) + ... + An算这个2^k有一个快捷的办法,定义一个函数如下即可:int lowbit(int x){return x&(x^(x–1));}当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:step1:令sum = 0,转第二步;step2:假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;step3: 令n = n –lowbit(n),转第二步。

可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:n = n –lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。

而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。

那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。

树状数组

树状数组

树 状
围内的数据之和.
修改一个位置上数字的值,就是修改一个 叶子结点的值,而当程序由叶子结点返回
数 组
根节点的同时顺便修改掉路径上的结点的 a数组的值.
对于询问的回答,可以直接查找i..j范围
的 运
内的值,遇到分叉时就兵分两路,最后在合 起来.也可以先找出0..i-1的值和0..j的 值,两个值减一减就行了.后者的实际操作

每个星星有个坐标。 如果一个星星的左下方(包含正左和正下)有k颗

星星,就说这颗星星是k级的.比如,在下面的例

图中,星星5是3级的(1,2,4在它左下)。 星星2,4是1级的。例图中有1个0级,2个1级,1

个2级,1个3级的星。



求出各个级别的星星的个数
树 状
算法有很多种,最实用的是树状数 组

如果直接做的话,修改的复杂度

是O(1),询问的复杂度是O(N),M

次询问的复杂度是M*N.M,N的

范围可以有100000以上


用线段树可以这样解:树源自若要维护的序列范围是0..5,先构造下面

的一棵线段树:





可以看出,这棵树的构造用二分便可以实 现.复杂度是2*N.
每个结点用数组a来表示该结点所表示范

while k<=n do

begin

C[k]:=C[k]+delta; k:=k+lowbit(k);
End;
End;
若要求I..j的元素和的值,则求出
1..I-1的值和1..j的值.

树状数组维护区间最值模板

树状数组维护区间最值模板

树状数组维护区间最值模板1. 什么是树状数组?树状数组(Fenwick Tree),也称为二进制索引树(Binary Indexed Tree,BIT),是一种用于高效计算前缀和、区间和以及维护区间最值的数据结构。

它可以在O(logn)的时间复杂度内完成单点更新和查询操作。

2. 树状数组的原理树状数组的核心思想是利用二进制位的特性来进行高效计算。

它将原始序列分解为多个子序列,并利用每个子序列的前缀和来表示该子序列中元素的累加和。

具体地,设原始序列为a[1…n],树状数组C[1…n]用于存储每个子序列的前缀和。

其中,C[i]表示以a[i]为末尾元素的子序列的累加和。

对于某个位置i,其对应的父节点位置可以通过将i的二进制表示中最低位1变为0来计算得到。

例如,对于二进制表示为1010(十进制为10)的位置i,其父节点位置为1000(十进制为8)。

树状数组支持两种操作:单点更新和区间查询。

•单点更新:当某个位置i发生变化时,需要更新其对应的所有父节点位置的值。

具体操作为将i的二进制表示中最低位1变为0,并依次向上更新。

•区间查询:要求计算区间[l, r]的和(或最值),可以通过计算C[r] - C[l-1]得到。

其中,C[r]表示以a[r]为末尾元素的子序列的累加和,C[l-1]表示以a[l-1]为末尾元素的子序列的累加和。

这样计算得到的差值即为区间[l, r]内元素的累加和。

3. 树状数组维护区间最值模板树状数组不仅可以用于维护前缀和,还可以用于维护区间最值。

以下是树状数组维护区间最值的模板代码:const int MAXN = 100000; // 树状数组大小int a[MAXN]; // 原始序列int C[MAXN]; // 树状数组// 单点更新操作void update(int pos, int val) {while (pos <= MAXN) {C[pos] = max(C[pos], val); // 维护区间最大值,如果是求和则改为C[pos]+= valpos += lowbit(pos); // 计算下一个父节点位置}}// 区间查询操作int query(int pos) {int res = 0;while (pos > 0) {res = max(res, C[pos]); // 维护区间最大值,如果是求和则改为res+=C[pos] pos -= lowbit(pos); // 计算下一个查询位置}return res;}// 计算pos的二进制表示中最低位1所对应的值int lowbit(int pos) {return pos & -pos;}4. 树状数组维护区间最值模板的使用示例以下是一个使用树状数组维护区间最值模板的示例,用于求解原始序列中某个区间内的最大值:#include <iostream>using namespace std;const int MAXN = 100000; // 树状数组大小int a[MAXN]; // 原始序列int C[MAXN]; // 树状数组// 单点更新操作void update(int pos, int val) {while (pos <= MAXN) {C[pos] = max(C[pos], val);pos += lowbit(pos);}}// 区间查询操作int query(int pos) {int res = 0;while (pos > 0) {res = max(res, C[pos]);pos -= lowbit(pos);}return res;}// 计算pos的二进制表示中最低位1所对应的值int lowbit(int pos) {return pos & -pos;}int main() {int n; // 序列长度cin >> n;for (int i = 1; i <= n; i++) {cin >> a[i];update(i, a[i]);}int q; // 查询次数cin >> q;while (q--) {int l, r; // 查询区间cin >> l >> r;int max_val = query(r) - query(l-1);cout << "区间最大值:" << max_val << endl;}return 0;}5. 总结树状数组是一种高效的数据结构,适用于解决多种问题,包括维护前缀和、区间和以及维护区间最值等。

树状数组求区间和的一些常见模型

树状数组求区间和的一些常见模型

树状数组求区间和的一些常见模型树状数组在区间求和问题上有大用,其三种复杂度都比线段树要低很多……有关区间求和的问题主要有以下三个模型(以下设A[1..N]为一个长为N的序列,初始值为全0):(1)“改点求段”型,即对于序列A有以下操作:【1】修改操作:将A[x]的值加上c;【2】求和操作:求此时A[l..r]的和。

这是最容易的模型,不需要任何辅助数组。

树状数组中从x开始不断减lowbit(x)(即x&(-x))可以得到整个[1..x]的和,而从x开始不断加lowbit(x)则可以得到x的所有前趋。

代码:操作【1】:ADD(x, c);操作【2】:SUM(r)-SUM(l-1)。

(2)“改段求点”型,即对于序列A有以下操作:【1】修改操作:将A[l..r]之间的全部元素值加上c;【2】求和操作:求此时A[x]的值。

这个模型中需要设置一个辅助数组B:B[i]表示A[1..i]到目前为止共被整体加了多少(或者可以说成,到目前为止的所有ADD(i, c)操作中c的总和)。

则可以发现,对于之前的所有ADD(x, c)操作,当且仅当x>=i时,该操作会对A[i]的值造成影响(将A[i]加上c),又由于初始A[i]=0,所以有A[i] = B[i..N]之和!而ADD(i, c)(将A[1..i]整体加上c),将B[i]加上c即可——只要对B数组进行操作就行了。

这样就把该模型转化成了“改点求段”型,只是有一点不同的是,SUM(x)不是求B[1..x]的和而是求B[x..N]的和,此时只需把ADD和SUM中的增减次序对调即可(模型1中是操作【1】:ADD(l-1, -c); ADD(r, c);操作【2】:SUM(x)。

(3)“改段求段”型,即对于序列A有以下操作:【1】修改操作:将A[l..r]之间的全部元素值加上c;【2】求和操作:求此时A[l..r]的和。

这是最复杂的模型,需要两个辅助数组:B[i]表示A[1..i]到目前为止共被整体加了多少(和模型2中的一样),C[i]表示A[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。

二维树状数组详解

二维树状数组详解

⼆维树状数组详解%%⼤连市理科状元。

“⾼级”数据结构——树状数组!※本⽂⼀切代码未经编译,不保证正确性,如发现问题,欢迎指正!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() {...}。

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

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

树状数组维护区间和的模型及其拓广的简单总结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,即可完成任务。

POJ3468就是这个功能的裸题,下面给出代码。

树状数组三道模板题小结

树状数组三道模板题小结

树状数组三道模板题⼩结今天写了⼀天北京信息科技⼤学,难度不⼤,跟西电校赛风格类似,⼤部分为数学题与规律题。

(改⽇更)其中的I题为树状数组/线段树模板题,发现之前没有在博客总结,⼀时也找不到好的模板,于是重新深⼊学习了⼀番。

⾸先引⼊洛⾕上的模板题:P3374 【模板】树状数组 1题⽬描述如题,已知⼀个数列,你需要进⾏下⾯两种操作:1.将某⼀个数加上x2.求出某区间每⼀个数的和输⼊输出格式输⼊格式:第⼀⾏包含两个整数N、M,分别表⽰该数列数字的个数和操作的总个数。

第⼆⾏包含N个⽤空格分隔的整数,其中第i个数字表⽰数列第i项的初始值。

接下来M⾏每⾏包含3个整数,表⽰⼀个操作,具体如下:操作1:格式:1 x k 含义:将第x个数加上k操作2:格式:2 x y 含义:输出区间[x,y]内每个数的和输出格式:输出包含若⼲⾏整数,即为所有操作2的结果。

说明时空限制:1000ms,128M数据规模:对于30%的数据:N<=8,M<=10对于70%的数据:N<=10000,M<=10000对于100%的数据:N<=500000,M<=500000题解由于是【单点修改,区间查询】裸题,就直接上AC代码了:#include<iostream>#include<cstdio>using namespace std;#define lowbit(x) ((x)&(-x))int n, m;int C[500010]; // 树状数组只需要开⼀倍内存空间int sum(int x) {int res = 0;while(x) {res += C[x];x -= lowbit(x);}return res;}void add(int x, int d) {while(x<=n) {C[x] += d;x += lowbit(x);}}int main() {cin>>n>>m;for(int i=1;i<=n;i++) {int ai;scanf("%d", &ai);add(i, ai);}while(m--) {int q, x, y;scanf("%d %d %d", &q, &x, &y);if(q==1) {add(x, y);}else if(q==2) {printf("%d\n", sum(y)-sum(x-1));}}return0;}P3368 【模板】树状数组 2题⽬描述如题,已知⼀个数列,你需要进⾏下⾯两种操作:1.将某区间每⼀个数数加上x2.求出某⼀个数的值输⼊输出格式输⼊格式:第⼀⾏包含两个整数N、M,分别表⽰该数列数字的个数和操作的总个数。

树状数组线段树介绍

树状数组线段树介绍


该题为二维树状数组的基本操作,套模板即可。
树状数组例题分析

二维区间查数:如图
树状数组例题分析

例 4(poj 2352 Stars)
天空中有一些星星,这些星星都在不同的位置,每个星星 有个坐标。如果一个星星的左下方(包含正左和正下) 有k颗星星,就说这颗星星是k级的.比如,在下面的例 图中,星星5是3级的(1,2, 4在它左下)。 星星2 ,4是1级的。例图中有1 个0级,2个 1级,1个2 级, 1个3 级的星。 求出各个级别的星星的个数

更新信息规律分析
同样整数n表示为二进制形式 如: 5 101 c[5],c[6],c[8],…… 6 110 c[6],c[8],c[16], …… 7 111 c[7],c[8],c[16], …… …… 规律:n每次增加最低位的1,增到最大为止 结论:更新复杂度 O(log n)

树状数组基本操作
树状数组简单介绍
问题引入

已知一个数组a[],要求完成一下的操作: 1、第i个元素add m; 2、区间【x, y】内的每个元素add m; 3、查询区间【x, y】内所有元素的sum; . . .
基于以上问题设计数据结构

设计一:基于简单数据结构a[] 复杂度分析: O(1), O(n), O(n)…… 设计二:基于简单数据结构sum[] 复杂度分析: O(n), O(n), O(1)……

树状数组简单介绍
建立 c[],其中 C[i]=a[i-2k+1]+……+a[i](k 为i在二进 制形式下末尾0的个数)。 例如: c[1]=a[1] c[2]=a[1]+a[2]=c[1]+a[2] c[3]=a[3] c[4]=a[1]+a[2]+a[3]+a[4]=c[2]+c[3]+a[4] c[5]=a[5] c[6]=a[5]+a[6]=c[5]+a[6] ………………

树状数组简介

树状数组简介

树状数组简介江苏省常州高级中学曹文一、前言我们平日里总是会遇到一些动态的求和问题,一些朴素的方法时间速度上很难让人满意,因此我们需要设计一些算法来解决所遇到的问题。

1、 基本问题我们下面设法解决这个动态求和问题。

我们定义 N 个整型变量组成的数组 v[1..N],每次操作有两种:(1) 将v[idx] 增加 det(2) 求 ∑=idxi i v 1][2、 朴素解决朴素的解决这个问题,我们用数组记录当前的数值,然后依次求和。

那么操作(1)的复杂度是 O(1),操作(2)的复杂度是 O(idx),令询问数为 Q ,则在最坏情况下为 O(QN)。

这样的复杂度非常不理想,我们要对之优化!二、倍增,分割和优化很多时候在处理区间问题的时候,倍增思想都是非常常用的,比如 RMQ 和LCA 问题,而分割方法也是一个有用的工具,比如线段树。

我们知道线段树是能够解决上面这个基本问题的,但是考虑到线段树的代码比较复杂,这里我们讨论更轻松的方法。

1、 第一个想法我们发现,在朴素算法中,操作(2)用了很多的时间,我们试着优化这个操作的算法。

参照RMQ 的思想,我们可以将一个区间分成一些长度为2的整次幂的小区间。

我们定义f[i][j] 表示区间 [I, i+2^j-1] 的和∑=-+jk k i v 21]1[,那么如果我们能够得到所有 f[i][j] ,那么我们就能够在 O(log N) 的时间里计算出我们需要的和。

具体方法如下:对于区间 [L,R],我们找到最大的 p ,使得 2^p <= R-L+1,而 sum[L,L+2^p-1] =f[L][p],于是我们只要继续计算 [L+2^p,R]就可以了。

由于每次找最大的 p,因次至多计算 log N 次就能得到结果——这就是倍增思想的应用。

但是很不幸,我们再观察操作(1)的时候,我们发现由于和某一个位置相关的 f[i][j] 实在太多了,所以操作(1)的时间就花的过多了,问题并没有得到解决。

树状数组详解

树状数组详解

树状数组详解在我们考虑到要对⼀个区间进⾏操作的时候,第⼀解法就是想到运⽤暴⼒⼤法,但是难免会⾯临着超时的危险。

有没有⼀种⽅法可以很好的解决这种区间操作呢?⾸先先考虑⼀下这三个问题:问题⼀:(1)有⼀个机器,⽀持两种操作,在区间[1,10000]上进⾏。

操作A:把位置x的值+k操作B:询问区间[l,r]所有数字之和区间的初始值全部为0现在你要充当这个机器,操作A和操作B会被穿插着安排给你,要求对于所有操作B,给出正确的答案。

怎样做才能最节省精⼒?问题⼆:(2)有⼀个机器,⽀持两种操作,在区间[1,10000]上进⾏。

操作A:把区间[l,r]的值全都+x操作B:询问位置x的值。

区间的初始值全部为0现在你要充当这个机器,操作A和操作B会被穿插着安排给你,要求对于所有操作B,给出正确的答案。

怎样做才能最节省精⼒?问题三:(3)有⼀个机器,⽀持两种操作,在区间[1,10000]上进⾏。

操作A:把区间[l,r]的值全都+x操作B:询问区间[l,r]所有数字之和区间的初始值全部为0现在你要充当这个机器,操作A和操作B会被穿插着安排给你,要求对于所有操作B,给出正确的答案。

怎样做才能最节省精⼒?三个问题中操作的数量都可以认为是10000(甚⾄有可能会更⼤)注意:1.举个例⼦,进⾏这种类似的操作:从⼀⾏任意打乱的数字中找⼀个数字不能认为⼀瞬间就可以找到,在这⾥所花费的精⼒和数字的总数具有线性关系。

2.我们认为将数据转换为⼆进制不需要任何时间。

对于问题1,如果我们每种操作都暴⼒进⾏,那么显然总的时间复杂度为O(mA+n*mB),n表⽰区间长度,mA表⽰操作A执⾏的次数,mB表⽰操作B执⾏的次数。

那么有没有⼀种更加轻松的办法呢?我们将引⼊⼀种数据结构,叫做<树状数组>。

⾸先了解⼀下在整个树状数组知识中起到⾮常重要的作⽤的⼀个东西,可所谓是树状数组的核⼼,应⽤的却说很巧妙。

lowbit(x)=x&((~x)+1) (为了少引⼊补码的概念,我们这⾥稍微⿇烦了⼀下,其实x&-x就⾏)它的作⽤是什么呢?它只保留“从低位向⾼位数,第⼀个数字1”作为运算结果⽐如⼆进制数00011100,执⾏这个函数后,结果就是00000100,也就是4。

树状数组 详解

树状数组 详解

树状数组详解对于普通数组,其修改的时间复杂度位O(1),而求数组中某一段的数值和的时间复杂度为O(n),因此对于n的值过大的情况,普通数组的时间复杂度我们是接受不了的。

在此,我们引入了树状数组的数据结构,它能在O(logn)内对数组的值进行修改和查询某一段数值的和。

树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。

假设A[]数组为存储原来的值得数组,C[]为树状数组。

我们定义:C[i] = A[i - 2^k + 1] + ..... + A[i] 其中k为i用二进制表示时的末尾0的个数。

例如:i= 10100,则k = 2,i = 11000,则k = 3;为了方便我直接写的是二进制的i,请读者注意。

此时我们可以知道,C[i] 它里面包含了2^k个A[]元素,这2^k个元素是从C[i]往后一直递减的2^k个元素,即i 一直减小的。

其中我们有一种快速的求解2^k的值得方法:[cpp]view plaincopyprint?利用机器的补码原理也可以写成这样:[cpp]view plaincopyprint?下面我们还要求的就是如何快速的修改某一个元素的值以及求出某一段元素值的和;先来看它是怎么快速修改某一个元素的值的:我们举个例子(为了方便i的值直接写成二进制了):i = 11000,此时k = 3;这2^k = 3 个数即为:A[11000],A[10111],A[10110],A[10101],A[10100],A[10011],A[10010],A[10001] C[11000] = A[11000]+A[10111]+A[10110]+A[10101]+A[10100]+A[10011]+A[10010]+A[10001];这里我们会发现:A[10100] + A[10011] + A[10010] + A[10001] = C[10100]而A[10010] + A[10001] = C[10010]A[10011] = C[10011]所以C[10100] = C[10010] + C[10011] + A[10100]又A[10110] + A[10101] = C[10110]而A[10101] = C[10101]所以C[10110] = C[10101] + A[10110]又A[10111] = C[10111]至此我们可以得出:C[11000] = C[10100] + C[10110] + C[10111] + A[11000]到这里我们可以得出:k的值就表示子树的个数,子树即为树状数组的元素。

求区间不同数字的个数和和

求区间不同数字的个数和和

求区间不同数字的个数和和1 Byte = 8 Bit1 KB = 1,024 Bytes1 MB = 1,024 KB = 1,048,576 Bytes⼀个int是4字节即4Byte.求个数和求和类似,不过是在更新的时候把1变成了这个数的值,下⾯就讲讲求区间不同数字的个数。

⾸先我们思考对于右端点固定的区间(即R确定的区间),我们如何使⽤线段树来解决这个问题。

我们可以记录每个数字最后⼀次出现的位置。

⽐如,5这个数字最后⼀次出现在位置3上,就把位置3记录的信息++(初始化为0)。

⽐如有⼀个序列 1 2 2 1 3 那么我们记录信息的数列就是 0 0 1 1 1 (2最后出现的位置是位置3 1最后出现的位置是位置4 3最后出现的位置是位置5)。

那么对区间 [1,5] , [2,5] , [3,5] , [4,5] , [5,5]的数字种数,我们都可以⽤sum[5]-sum[x-1]来求(sum数组记录的是前缀和)(前缀和之差可以⽤线段树或者树状数组来求)。

那么对着区间右端点会变化的题⽬,我们应该怎么办呢?先思考⼀下如果右端点有序的话,我们可以怎么做。

对R不同的区间,向线段树或者树状数组中添加元素,知道右端点更新为新的R,在添加的过程中,如果这个元素之前出现过,就把之前记录的位置储存的信息 -1,然后在新的位置储存的信息 +1,这样就可以保证在新的右端点固定的区间⾥,记录的是数字最后⼀次出现的位置的信息,这样题⽬就解决了。

也就是说对于这个题⽬,我们也可以不⽤主席树,只要对询问排序,然后利⽤树状数组或者线段树就可以解决这个问题。

(离线解法)如果不对询问排序的话,我们就必须要⽤主席树来解决这个问题了,对每个右端点建⽴⼀个线段树。

不断查询即可。

(在线解法)给你 n 个数,然后有 q 个询问,每个询问会给你[l,r],输出[l,r]之间有多少种数字。

#include<bits/stdc++.h>//主席树using namespace std;#define ll long long#define fuck(x) cout<<#x<<" "<<x<<endl;const int maxn=30000+10;int d[4][2]={1,0,-1,0,0,1,0,-1};int a[maxn],v[maxn],rt[maxn],vis[(int)1e6+10],tot,n;struct node{int ls,rs,sum;node(int ls=0,int rs=0,int sum=0){this->ls=ls;this->rs=rs;this->sum=sum;}}tree[maxn*30];int build1(int l,int r){int t=++tot;tree[t].sum=0;if(l!=r){int mid=(l+r)>>1;tree[t].ls=build1(l,mid);tree[t].rs=build1(mid+1,r);}return t;}int build2(int l,int r,int last,int pos,int val){int t=++tot;tree[t]=tree[last];tree[t].sum+=val;if(l!=r){int mid=(l+r)>>1;if(pos<=mid)tree[t].ls=build2(l,mid,tree[last].ls,pos,val);elsetree[t].rs=build2(mid+1,r,tree[last].rs,pos,val);}return t;}int query(int L,int R,int l,int r,int t){if(l<=L&&r>=R)return tree[t].sum;int mid=(L+R)>>1,ans=0;if(r<=mid)ans+=query(L,mid,l,r,tree[t].ls);if(l>mid)ans+=query(mid+1,R,l,r,tree[t].rs);else{ans+=query(L,mid,l,r,tree[t].ls);ans+=query(mid+1,R,l,r,tree[t].rs);}return ans;}int main(){int n,q;scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&(a[i]));scanf("%d",&q);rt[0]=build1(1,n);for(int i=1;i<=n;i++){if(!vis[a[i]])rt[i]=build2(1,n,rt[i-1],i,1);else{rt[i]=build2(1,n,rt[i-1],vis[a[i]],-1);rt[i]=build2(1,n,rt[i],i,1);}vis[a[i]]=i;}while(q--){int x,y;scanf("%d%d",&x,&y);printf("%d\n",query(1,n,x,y,rt[y]));}return 0;}给⼀段n长度的数字序列,以及q次区间询问,问区间不同数字⼤⼩之和。

【ZJOI2017】树状数组

【ZJOI2017】树状数组

【ZJOI2017】树状数组题⽬描述漆⿊的晚上,九条可怜躺在床上辗转反侧。

难以⼊眠的她想起了若⼲年前她的⼀次悲惨的 OI ⽐赛经历。

那是⼀道基础的树状数组题。

给出⼀个长度为 n 的数组 A ,初始值都为 0,接下来进⾏ m 次操作,操作有两种:1 x , 表⽰将 A x 变成 (A x +1)mod 2。

2 l r , 表⽰询问 (∑r i =l A i )mod 2。

尽管那个时候的可怜⾮常的 simple ,但是她还是发现这题可以⽤树状数组做。

当时⾮常 young 的她写了如下的算法:其中 lowbit(x ) 表⽰数字 x 最低的⾮ 0 ⼆进制位,例如 lowbit(5)=1,lowbit(12)=4。

进⾏第⼀类操作的时候就调⽤ Add(x ),第⼆类操作的时候答案就是 Query(l ,r )。

如果你对树状数组⽐较熟悉,不难发现可怜把树状数组写错了:Add 和 Find 中 x 变化的⽅向反了。

因此这个程序在最终测试时华丽的爆 0 了。

然⽽奇怪的是,在当时,这个程序通过了出题⼈给出的⼤样例——这也是可怜没有进⾏对拍的原因。

现在,可怜想要算⼀下,这个程序回答对每⼀个询问的概率是多少,这样她就可以再次的感受到⾃⼰是⼀个多么⾮的⼈了。

然⽽时间已经过去了很多年,即使是可怜也没有办法完全回忆起当时的⼤样例。

幸运的是,她回忆起了⼤部分内容,唯⼀遗忘的是每⼀次第⼀类操作的 x 的值,因此她假定这次操作的 x 是在 [l i ,r i ] 范围内等概率随机的。

具体来说,可怜给出了⼀个长度为 n 的数组 A ,初始为 0,接下来进⾏了 m 次操作:1 l r , 表⽰在区间 [l ,r ] 中等概率选取⼀个 x 并执⾏ Add(x )。

2 l r , 表⽰询问执⾏ Query(l ,r ) 得到的结果是正确的概率是多少。

输⼊格式第⼀⾏输⼊两个整数 n ,m 。

接下来 m ⾏每⾏描述⼀个操作,格式如题⽬中所⽰。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

树状数组求区间和的一些常见模型
树状数组在区间求和问题上有大用,其三种复杂度都比线段树要低很多……有关区间求和的问题主要有以下三个模型(以下设A[1..N]为一个长为N的序列,初始值为全0):
(1)“改点求段”型,即对于序列A有以下操作:
【1】修改操作:将A[x]的值加上c;
【2】求和操作:求此时A[l..r]的和。

这是最容易的模型,不需要任何辅助数组。

树状数组中从x开始不断减lowbit(x)(即x&(-x))可以得到整个[1..x]的和,而从x开始不断加lowbit(x)则可以得到x 的所有前趋。

代码:
void ADD(int x, int c)
{
for (int i=x; i<=n; i+=i&(-i)) a[i] += c;
}
int SUM(int x)
{
int s = 0;
for (int i=x; i>0; i-=i&(-i)) s += a[i];
return s;
}
操作【1】:ADD(x, c);
操作【2】:SUM(r)-SUM(l-1)。

(2)“改段求点”型,即对于序列A有以下操作:
【1】修改操作:将A[l..r]之间的全部元素值加上c;
【2】求和操作:求此时A[x]的值。

这个模型中需要设置一个辅助数组B:B[i]表示A[1..i]到目前为止共被整体加了多少(或者可以说成,到目前为止的所有ADD(i, c)操作中c的总和)。

则可以发现,对于之前的所有ADD(x, c)操作,当且仅当x>=i时,该操作会对A[i]
的值造成影响(将A[i]加上c),又由于初始A[i]=0,所以有A[i] = B[i..N]之和!而ADD(i, c)(将A[1..i]整体加上c),将B[i]加上c即可——只要对B数组进行操作就行了。

这样就把该模型转化成了“改点求段”型,只是有一点不同的是,SUM(x)不是求B[1..x]的和而是求B[x..N]的和,此时只需把ADD和SUM中的增减次序对调即可(模型1中是ADD加SUM减,这里是ADD减SUM加)。

代码:
void ADD(int x, int c)
{
for (int i=x; i>0; i-=i&(-i)) b[i] += c;
}
int SUM(int x)
{
int s = 0;
for (int i=x; i<=n; i+=i&(-i)) s += b[i];
return s;
}
操作【1】:ADD(l-1, -c); ADD(r, c);
操作【2】:SUM(x)。

(3)“改段求段”型,即对于序列A有以下操作:
【1】修改操作:将A[l..r]之间的全部元素值加上c;
【2】求和操作:求此时A[l..r]的和。

这是最复杂的模型,需要两个辅助数组:B[i]表示A[1..i]到目前为止共被整体加了多少(和模型2中的一样),C[i]表示A[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。

对于ADD(x, c),只要将B[x]加上c,同时C[x]加上c*x即可(根据C[x]和B[x]间的关系可得);
而ADD(x, c)操作是这样影响A[1..i]的和的:若x<i,则会将A[1..i]的和加上x*c,否则(x>=i)会将A[1..i]的和加上i*c。

也就是,A[1..i]之和= B[i..N]之和* i + C[1..i-1]之和。

这样对于B和C两个数组而言就变成了“改点求段”(不过B是求后缀和而C是求前缀和)。

另外,该模型中需要特别注意越界问题,即x=0时不能执行SUM_B操作和ADD_C 操作!代码:
void ADD_B(int x, int c)
{
for (int i=x; i>0; i-=i&(-i)) B[i] += c;
}
void ADD_C(int x, int c)
{
for (int i=x; i<=n; i+=i&(-i)) C[i] += x * c;
}
int SUM_B(int x)
{
int s = 0;
for (int i=x; i<=n; i+=i&(-i)) s += B[i];
return s;
}
int SUM_C(int x)
{
int s = 0;
for (int i=x; i>0; i-=i&(-i)) s += C[i];
return s;
}
inline int SUM(int x)
{
if (x) return SUM_B(x) * x + SUM_C(x - 1); else return 0; }
操作【1】:
ADD_B(r, c); ADD_C(r, c);
if (l > 1) {ADD_B(l - 1, -c); ADD_C(l - 1, -c);}
操作【2】:SUM(r) - SUM(l - 1)。

相关文档
最新文档