树状数组

合集下载

树状数组及其应用

树状数组及其应用

树状数组及其应用(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 什么是树状数组树状数组(Fenwick Tree),也叫做树形数组,是一种支持单点修改、区间查询的数据结构。

这种数据结构可以在O(log n)的时间复杂度内实现单点修改和区间查询,相较于线段树,树状数组实现较为简单。

2 树状数组的实现原理树状数组的核心思想是利用二进制中的位运算来维护前缀区间和。

假设有一个数组a[],其前缀和为sum[],那么对于每个下标i,只需要维护[1,i]这个区间的和,即sum[i]。

这样,当需要查询区间[1,r]的和时,只需要计算sum[r]即可,不需要遍历每一个元素。

对于单点修改操作,假设我们要将数组a[]的第i个元素修改为x,我们只需要将第i个元素的值改为x,并且要将[1,i]这个区间内所有的sum数组的值都加上x-a[i],即将差值x-a[i]加到了[1,i]区间中。

3 树状数组的实现细节3.1 树状数组的初始化树状数组需要初始化为0,因为我们需要维护的是每个元素的前缀和,如果没有初始化为0,其前缀和也就变得不可预测了。

查询区间[1,r]的和时,只需要计算sum[r]即可。

具体实现过程为:从r开始向前跳,每一个位置的前缀和都加到一个答案变量ans中,直到跳到0为止。

可以使用以下的代码实现:```C++int query(int x){int ans = 0;while(x > 0){ans += tree[x];x -= lowbit(x);}return ans;}```修改操作也很简单,只需要将第i个位置的值改为x,然后将[1,i]这个区间内所有的sum数组的值都加上x-a[i],即将差值x-a[i]加到了[1,i]区间中,可以使用以下的代码实现:```C++void update(int x, int val){while(x <= n){tree[x] += val;x += lowbit(x);}}```4 树状数组与线段树的比较相对于线段树,树状数组的实现更为简单,而且更加省空间。

树状数组区间求和

树状数组区间求和

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

它可以在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. 数组排序:树状数组可以高效地对一个数组进行排序,将数组中的元素按照某个规则重新排列。

数据结构之树状数组

数据结构之树状数组

数据结构之树状数组咱今天来聊聊数据结构里一个特别有趣的家伙——树状数组。

要说这树状数组啊,就像是我们生活中的小帮手,能帮我们解决好多看似复杂的问题。

我想起之前有一次,学校组织数学竞赛,其中有一道题可把好多同学都难住了。

题目是这样的:给定一个整数数组,要快速计算出某一段区间内数字的和。

当时好多同学都用最笨的办法,一个个数字去加,费了好大劲还容易出错。

这时候,树状数组就派上用场啦!它就像一个聪明的小管家,能快速又准确地算出我们想要的结果。

树状数组的原理其实不难理解。

想象一下,我们把数组里的数字看成是一棵棵小树苗,这些小树苗按照一定的规则排列成了一片小树林。

通过巧妙的设计,我们能快速找到想要的那一片小树苗的总和。

比如说,我们有一个数组 1, 3, 5, 7, 9 ,用树状数组来表示的话,就会有一番特别的构造。

它可不是简单地把数字罗列,而是通过一些巧妙的计算和存储方式,让我们在查找区间和的时候能够迅速得到答案。

树状数组的更新操作也挺有意思。

就好比我们在小树林里新种了一棵小树苗,或者给某一棵小树苗又长高了一些,我们得及时更新整个树林的状态,让它能准确反映出最新的情况。

而且,树状数组在解决实际问题的时候,那叫一个高效。

比如计算一个班级学生某一段时间内的考试成绩总和,或者统计一个月内某种商品的销售总量在不同时间段的情况。

再想想,如果把树状数组比作一个神奇的魔法盒子,我们输入一些数字,它就能按照特定的魔法规则,迅速给我们吐出我们想要的结果。

总之啊,树状数组虽然看起来有点神秘,但只要我们深入了解它,就会发现它其实特别有趣又实用。

就像我们在生活中,遇到各种难题,只要找到了合适的方法和工具,就能轻松解决。

希望大家都能跟树状数组成为好朋友,让它帮助我们在数据的世界里畅游,解决更多有趣的问题!。

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

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

树状数组维护区间最值模板什么是树状数组?树状数组(Fenwick Tree),也叫做Binary Indexed Tree(BIT),是一种用于高效计算数组前缀和的数据结构。

其主要特点是可以在O(logn)的时间复杂度内进行单点更新和区间查询操作,相较于传统的线段树数据结构,树状数组更加简洁高效。

树状数组的实现原理树状数组的实现原理基于二进制的思想,比如一个数x,我们可以将其二进制表示的末尾的1和后面的0看作是一个整体,记作lowbit(x)。

那么对于一个位置i来说,其对应的lowbit(i)即为i二进制表示中的最低位的1所对应的数值。

利用这个性质,我们可以很巧妙地处理数组的下标。

树状数组的实现步骤1. 初始化首先需要对树状数组进行初始化,将其所有位置上的值都设置为0。

假设数组长度为n,则树状数组的长度应为n+1,这是因为我们需要使用1-based索引。

2. 单点更新单点更新操作主要用来修改树状数组中某个位置上的值。

假设要将数组中某个位置i上的值增加x,则需要执行以下操作:将i的值加上x,然后将i的lowbit(i)位置上的值也加上x。

此后,需要将i更新为i+lowbit(i),继续更新对应位置的值,直到i超出数组范围。

3. 前缀和计算前缀和操作主要用来计算数组中某个位置之前的所有元素之和。

假设要计算前缀和的区间为[1, k],则需要执行以下操作:将结果初始化为零,然后将k的值加入结果。

接下来,需要将k更新为k-lowbit(k),继续将k的值加入结果,直到k为0为止。

4. 区间查询区间查询操作主要用来查询数组中某个区间的最值。

假设要查询区间[1, k]中的最大值,可以先计算[1, k]的前缀和值,然后将其减去[1, k-1]的前缀和值,即可得到结果。

5. 示例代码下面是使用Python语言实现的树状数组的示例代码:class FenwickTree:def __init__(self, n):self.n = nself.tree = [0] * (n+1)def update(self, i, delta):while i <= self.n:self.tree[i] += deltai += i & -idef query(self, i):res = 0while i > 0:res += self.tree[i]i -= i & -ireturn res以上就是树状数组的维护区间最值模板的详细解释和实现步骤。

树状数组

树状数组

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。

c++树状数组讲解

c++树状数组讲解

树状数组(Binary Indexed Tree,简称BIT)是一种用于高效处理前缀和问题的数据结构。

它可以在O(log n)的时间复杂度内完成单点修改和前缀和查询操作。

下面是对C++中树状数组的详细讲解:1. 基本概念树状数组利用二进制的特点,将原数组进行拆分,并通过一系列辅助数组来快速计算前缀和。

每个辅助数组元素都表示原数组中某一段连续元素的和。

2. 核心操作2.1 单点修改当我们要修改原数组中的某个元素时,需要更新树状数组中与该元素相关的所有辅助数组元素。

这个过程可以通过一系列加法和位运算实现。

2.2 前缀和查询前缀和查询是树状数组的主要应用之一。

通过树状数组,我们可以在O(log n)的时间复杂度内计算出原数组中任意一段连续元素的和。

3. 实现细节3.1 初始化树状数组在使用前需要进行初始化,通常是将原数组的所有元素初始化为0。

3.2 低位和高位运算树状数组的实现中涉及到位运算,特别是与操作(&)和加操作(+)。

这些操作用于确定辅助数组元素的索引和更新范围。

3.3 单点修改函数通常,单点修改函数接受两个参数:要修改的元素的索引和新的值。

函数内部会计算需要更新的辅助数组元素的索引,并进行相应的加法操作。

3.4 前缀和查询函数前缀和查询函数接受一个参数:要查询的前缀的结束索引。

函数通过一系列位运算和加法操作,计算出原数组中从第一个元素到指定索引的元素之和。

4. 示例代码下面是一个简单的C++示例代码,展示了树状数组的基本实现:cpp#include<iostream>#include<vector>using namespace std;class BinaryIndexedTree {private:vector<int> bit;int n;int lowbit(int x) {return x & (-x);}public:BinaryIndexedTree(int _n) : n(_n + 1), bit(_n + 1, 0) {}void update(int idx, int val) {while (idx <= n) {bit[idx] += val;idx += lowbit(idx);}}int query(int idx) {int sum = 0;while (idx > 0) {sum += bit[idx];idx -= lowbit(idx);}return sum;}};int main() {int n;cout << "Enter the number of elements: ";cin >> n;BinaryIndexedTree bit(n);// Example usagebit.update(1, 5);bit.update(3, 7);cout << "Prefix sum from 1 to 3: " << bit.query(3) << endl; // Should output 12return0;}5. 总结树状数组是一种高效处理前缀和问题的数据结构,它利用二进制的特点进行拆分和计算。

树状数组延伸和离线优化(CDQ、整体二分和莫队)-myt

树状数组延伸和离线优化(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]。

树状数组

树状数组

树状数组是一个查询和修改复杂度都为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. 总结树状数组是一种高效的数据结构,适用于解决多种问题,包括维护前缀和、区间和以及维护区间最值等。

『树状数组』树状数组模板

『树状数组』树状数组模板

『树状数组』树状数组模板『树状数组』树状数组模板竞赛需要⽤到的点树状数组可以解决⼤部分基于区间上更新、查询的操作树状数组能写的线段树都能写,但线段树能写的树状数组不⼀定能写代码量⼩,且常数⽐线段树⼩树状数组是树与⼆进制的混合使⽤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() {...}。

树状数组(C++版)

树状数组(C++版)

树状数组第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的位置其实从我画的满二叉树中就可以看出来。

数据结构之树状数组

数据结构之树状数组

如果,我们要分析的是“改段求点”型,即
修改某一个区间,使其每个值都加上值val,然后
再去询问你某个点的值是多少。
问题的引入:
给定n个数,a[1]..a[n]。每次我们可能有
两种操作:
(1)将a[i]..a[j]的每个数都加上一个值;
(2)查询a[x]的值。
n的规模如果比较大(约100000)
该如何高效的实现?
令del[i]表示i..MN段被加了多少。则区间修
改[a,b]时,我们让del[a]++,del[b+1]--即可。
(此时树状数组是建立在del数组基础上的)
这个时候,要求某一个位置x的数的值时,用
Getsum(x)来表示。
(即a[x]=sigma(del[1]..del[x]))
问题的深入: 现在,我们如果可以对原数组中,进行区间 的修改,想了解区间的和,该如何去做? del[i]依旧表示i..MN段被加了多少。我们 想知道区间[L,R]的和是多少。这个可以用减法, 用[1,R]的和减去[1,L-1]的和。 提示:建立两棵树状数组
x+lowbit(x)为x的直接父亲编号 void update(int x,int v){ //在x处增加v for (; x<=MN; x+=x&(-x)) C[x]+=v; } //MN为树状数组C的上限
上面为更新C数组的函数
于是乎,最前面提到的问题就完美解决了。 初始化C数组全为0,然后针对每次操作, 操作(1):update(x, val); 操作(2):getsum(x)。 树状数组时间复杂度O(nlgn) ,简洁高效! 学完了树状数组的基本操作,下面来看看
树状数组可以发挥的强大作用吧!

树状数组简介

树状数组简介

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

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)的时间就花的过多了,问题并没有得到解决。

树状数组(二叉索引树)理解与分析

树状数组(二叉索引树)理解与分析

树状数组(二叉索引树)理解与分析文/龚健飞说起树状数组,或许很多人会对这个名称或者这个概念感兴趣。

但仅仅通过文字上的描述,初学者会比较难地理解到这个数据结构这个算法。

我凭自己的认识来阐述一下对这个数据结构的理解,希望给其他初学者一个参考的同时也给自己一个机会更好地认识它。

引言先用一个问题来引导出树状数组的概念。

给定一个n 个元素的的数组n A A A A ......210、、,对其进行操作。

按照情况,sum(n)表示计算n 0~A A 的和,add(x,d)表示让x A 加上d 。

显然,对于第一种操作,利用较为简单的前缀和数据可以在O(1)的时间内解决单次问题。

但对于第二种操作,前缀和在n 足够大的情况下显得无能为力。

那么,树状数组能否解决这个问题?要回答这个问题首先得弄清,什么是树状数组。

树状数组树状数组,顾名思义,即是将所有的数据排列成一棵树的形式,利用分支和源头(更确切地讲,是tree 。

)掌握数据信息以及进行操作,体现了一点分治的思想。

要特别指出,排列成树的依据并不是数据本身,而是数据的索引,即是index 。

程序如下:#include <iostream> using namespace std ;int a[10000]; int c[10000]; int n ;int lowbit(int x){ return x& -x ;//求出lowbit }int sum(int x){ int ret = 0 ; while(x > 0){ ret += c[x]; x -= lowbit(x) ; } ret += c[0] ;return ret ;//求和函数}void add(int x , int d){while(x <= n ){c[x] += d ;x += lowbit(x);//加法操作}}int main(){cin >> n ;for(int i = 0 ; i < n ; ++i) cin >> a[i] ;c[0] = a[0] ;for(int i = 1 ; i < n ; ++i){for(int j = i - lowbit(i) + 1 ; j <= i ; ++j)c[i] += a[j] ; //预处理}int ch ;cout << "select the operation" << endl << "1 for sum" << endl << "2 for add " << endl ;while(cin >> ch){if(ch == 1 ) {int x ;cin >> x ;cout << sum(x - 1) << endl ;}else {int x , d ;cin >> x >> d ;add(x - 1 , d) ;}cout << "select the operation" << endl << "1 for sum" << endl << "2 for add " << endl ;}return 0 ;}lowbit(x)某些书上定义lowbit(x)为,x的二进制表达式中最右边的1以及后面的0组成的数(显然,这些lowbit只能是1、2、4、8......n2)。

树状数组详解

树状数组详解

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

有没有⼀种⽅法可以很好的解决这种区间操作呢?⾸先先考虑⼀下这三个问题:问题⼀:(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)的时间,针对于某些题⽬,数据量奇⼤⽆⽐,必然会TLE。

所以我们使⽤树状数组来优化这两个操作,使得修改和查询均可以在O(logn)的时间内完成,提升效率。

(这是百度百科上树状数组的图)可以直观地看出树状数组是个什么模式,是的,这就是⼀棵树,⽽这棵树上每个节点存储的数据就是它所有⼉⼦节点的数据和。

所以我们就可以在树上做修改和区间查询(节点减节点),来做到树状数组这种优化⽅式。

树状数组的实现需要三个特别重要的函数,我⼀般把它们写成fix()(向上修改),getsum()(向下查询),和lowbit()(这个函数是理解树状数组运⾏过程的关键)。

lowbit()函数⾸先从最关键的lowbit函数⼊⼿。

它长这个样⼦:int lowbit(int x){return x+=x&-x;}这⾥还涉及到了位运算的相关知识,所谓x&-x,是在⼆进制上的运算操作,它的实现也很简单,把x按位取反,最后同时上移最后的那个1。

⽐如lowbit(34)最终的结果就是36。

那么这个lowbit函数是⼲什么⽤的呢?关于lowbit运算,有不太了解的⼩伙伴可以参考本蒟蒻的这篇博客:我们可以把它理解成对树状数组的遍历⽅式。

根据树状数组的⽰意图可以发现,我们如果想对原数组进⾏元素修改,会牵连到于之关联的树状数组整个链。

所以我们必须层层向上修改,每⼀层都要修改,才能保证树状数组存储的元素的正确性。

那么这个层层向上(向下),就需要lowbit这个函数,或者是说这个功能,来实现。

fix()函数void fix(int x){for(int i=x;i<=n;i+=i&-i)c[i]++;}void fix(int x,int y)//表⽰在x元素处修改y个单位{for(int i=x;i<=n;i+=i&i)c[i]+=y;}通过刚才学习lowbit函数,我们应该可以理解这个循环的含义。

树状数组 详解

树状数组 详解

树状数组详解对于普通数组,其修改的时间复杂度位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. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

树状数组
武钢三中 吴豪【引言】
在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。

但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。

可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。

当n非常大时,程序会运行得非常缓慢。

因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。

【理论】
为了对树状数组有个形 象的认识,我们先看下面这张图。

如图所示,红色矩形表示的数组C[]就是树状数组。

这里,C[i]表示A[i-2^k+1]到A[i]的和,而k则是i在二进制时末尾0的个数,或者说是i用
2的幂方和表示时的最小指数。

( 当然,利用位运算,我们可以直接计算出2^k=i&(i^(i-1)) )同时,我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。

所以,当我们修
改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(logn)。

另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。

不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开
成2的幂方和时的项数,因此,求和操作的复杂度也是O(logn)。

接着,我们考察这两种操作下标变化的规律:
首先看修改操作:
已知下标i,求其父节点的下标。

我们可以考虑对树从逻辑上转化:
如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。

有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。

因而父节点下标 p=i+2^k (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)即 p = i + i&(i^(i-1)) 。

接着对于求和操作:
因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。

即 p = i - i&(i^(i-1)) 。

至此,我们已经比较详细的分析了树状数组的复杂度和原理。

在最后,我们将给出一些树状数组的实现代码,希望读者能够仔细体会其中的细节。

【代码】
求最小幂2^k:
in t L o wb i t(in t t)
{
retur n t & ( t ^ ( t - 1 ) );
}
求前n项和:
in t S um(in t e n d)
{
in t sum = 0;
wh il e(e n d> 0)
{
sum += in[e n d];
e n d -= L o wb i t(e n d);
}
retur n sum;
}
对某个元素进行加法操作:
v oi d pl us(in t po s , in t n um) {
wh il e(po s<= n)
{
in[po s] += n um;
po s += L o wb i t(po s);
}
}
以下内容转自O IBH
htt p://oi bh.o r g/bbs/v i ewthread.p h p?t i d=23806&h ig h lig ht=%CA%F7%D7%B4%CA%FD
%D7%E9
作者sa i901013
话说我学了这个树状数组N个日子了,还没有用过,今天一用,果然不凡...-_-只是PKU O J的E文让我比较抓狂...看了半天才知道它讲什么.这个题目乃入门题目,不赘述了...下面说一下我用树状数组的体会(语言比较难理解,字体比较难忍受= =,请原谅)..
什么叫艺术
做了这个题目之后,我对算法的艺术性又有了进一步的认识。

跟线段树的又长又臭相比,树状数组可谓娇小玲珑。

那简洁的代码,与Trea p、并查集等数据结构一样的美丽,清秀。

不仅如此,它有着简单易用的特点,容易记忆。

最大的艺术就在于k+=(-k)k这个变化量的设计,我们可以看到,(-k)&k完全等价于k&(k^(k-1)),两个可以随便记一 个,不过显然第一个更容易记忆。

虽然到现在我还不知道这两句是如何计算到“将k化为二进制数时末尾0的个数”(补充:可以参考本贴#17)=。

=! ,但我很享受这一种奇特的美妙。

什么叫速度
除了in sert()和g et()时间复杂度都是O(log n)之外,其实这个很正常,树状数组特别的是它编程复杂度离奇的小-_-//... .據说它的时间复杂度常数还比线段树小很多。

除此之外,它的空间复杂度也比线段树明显小约2/3。

下面分析一下我写的Co de。

1.
2.//先定义c[x]为树状数组,a[x]为原数组。

3.//其实这里叫rem o d i fy()函数比较合理,因为这里是修改某个元素而不是插入
4.//deta l是将a[k]变化deta l,而不是“变为deta l”
5.//m i x是要十分注意的,因为常常有人会被输入个数N所迷惑了
6.//因为修改c[k]的话对c[k+1]....c[m i x]有影响,所以不难想到k的变化为增变化
7.
8.v oi d in sert(in t*c,in t k,in t deta l){
9.f o r(; k<=m i x;k+=(-k)&k ) c[k] +=deta l;
10.}
11.
12.//顾名思义,g etsum()就自然是某一段的和
13.//不过比较奇怪,返回的是a[1]...a[k]
14.//其实并不难为,如果你想要a[j]...a[k],你可以g etsum(k)-g etsum(j)
15.
16.in t g etsum(in t*c,in t k){
17.in t t;
18.f o r(t=0; k>0 ;k-=(-k)k ) t +=c[k];
19.retur n t;
20.}
复制代码
可能你看某幅图会容易搞清楚啥是树状数组,但是我不打算贴这一幅图片,因为百度一下就可以找到。

---//这个这个....绝对不是我懒!
算了,还是贴一下

什么要注意
世界上没有免费的午餐,你喜欢上树状数组,你就一定要承受它的缺点,-_-//貌似喜欢MM也一样。

不过有一点我还没有绝对地搞清楚,有人说树状数组可以解“最长不下降子序列”问题,但这个应该会涉及到求某一个区间范围内最值。

但是貌似树状数组要符合减法原则,也就是说不能求某一个区间范围内最值。

初步来讲,我认为“树状数组解最长不下降子序列”和“减法原则”可能并不矛盾,可能有一种符合“减法原则”解法,我不做深入讨论探究了。

下面引用一段话:
在很多的情况下,线段树都可以用树状数组实现.凡是能用树状数组的一定能用线段树.当题目不满足减法原则的时候,就只能用线段树,不能用树状数组.例如数列操作如果让我们求出一段数
字中最大或者最小的数字,就不能用树状数组了。

除了上面的“减法原则”之外,还需要说明的是:树状数组由1开始。

习惯用C/C++的同志们申请地址的时候必须从0开始,自然而然地,我们习惯了第一个元素是0,但是
树状数组必须由1开始。

具体细节想一下就会知道,那是因为跟那个“将k化为二进制数时末尾0的个数”有关。

最后要说的是,干看是不行的,练习才是正道。

PKU O J 2352 S tars是一道不错的入门题目。

可以参考以下代码。

不过最好还是自己做啦...-_-///..
本文个人原创,如有雷同,肯定抄我sa i90.!
此外本人语言是比较难理解的,若各位高手们看得不顺眼,请见谅...
1.
2.#in c l ude<std io.h>
3.in t c[32003];
4.in t a[32003];
5.in t n,m i x=32003;
6.v oi d in sert(in t k,in t deta l)
7.{
8.f o r(; k<=m i x;k+=(-k)k ) c[k] +=deta l;
9.}
10.in t g etsum(in t k)
11.{
12.in t t;
13.f o r(t=0; k>0 ;k-=(-k)k ) t +=c[k];
14.retur n t;
15.}
16.in t ma in()
17.{
18.memset(c,0,s i ze o f(c));
19.memset(a,0,s i ze o f(a));
20.in t i,x,y;
21.sca n f("%d",n);
22.f o r(i=0; i<n ;i++)
23.{
24.sca n f("%d%d",x,&y);
25.x++;
26.a[g etsum(x)]++;
27.in sert(x,1);
28.}
29.f o r(i=0; i<n ;i++) p r in tf("%d\n",a);
30.retur n 0;
31.}。

相关文档
最新文档