树状数组




要想知道c[i]表示的是哪个区域的和,只要 求出2^k(lowbit); 树状数组之所以高效简洁的原因就是能 够利用位运算直接求出i对应的lowbit
int lowbit(int i) { return i & ( -i ); } 解释: 假设i为 则-i的 :1) 取反 2)+1
2^K(lowbit)求法
Thanks... ...
求出各个级别的星星的个数
题目分析:



算法有很多种,最实用的是树状数 组 由于本题的数据输入是以y坐标 的升序输入,所以,只需要比较 x坐标即可 树状数组存储的是某一段范围内 有多少个点,即求和.
小结


在很多的情况下,线段树都可以用树状 数组实现.凡是能用树状数组的一定能 用线段树.当题目不满足减法原则的时 候,就只能用线段树,不能用树状数组.例 如数列操作如果让我们求出一段数字中 最大或者最小的数字,就不能用树状数 组了. 树状数组的每个操作都是0(log(n))的复 杂度.
怎 么 办
用树状数组!!!

下图中的C数组就是树状数组,a 数组是原数组
先自己研究一下这个东西
可以发现这些规律 C1=a1 C2=a1+a2 C3=a3 C4=a1+a2+a3+a4 C5=a5 C6=a5+a6 …… C8=a1+a2+a3+a4+a5+a6+a7+a8 …… C2^n=a1+a2+….+a2^n
树状数组
先看一个例题: 数列操作: 给定一个初始值都为0的序列,动 态地修改一些位置上的数字,加 上一个数,减去一个数, 然后动态 地提出问题,问题的形式是求出 一段数字的和.

如果直接做的话,修改的复杂度 是O(1),询问的复杂度是O(N),M 次询问的复杂度是M*N.M,N的 范围可以有100000以上
int getSum(int x) { int sum= 0; for (int i = x; i > 0; i -= lowbit(i)) sum += c[i]; return sum; }
循环的次数为x的二进制表示 中1的个数
求和Sum
修改modify

修改了某个a[i],就需改动所有包含a[i]的c[j]; 从上图看就是要更改从改叶子节点到根节点路径上的所有c[j]

但是怎么求一个节点的父节点呢?
Thinking......
ห้องสมุดไป่ตู้

增加虚构点,变成满二叉树!!! 每个节点的父亲就跟其右兄弟一样了; 而左右兄弟的管辖区域是一样的; 找父节点 所以:i的父节点就是i+lowb(i);
void modify(int x,int delta) { for(int i = x; i<=n; i+=lowbit(i)) c[i] += delta; } /*其中n为数组的长度 时间复杂度同样是O(logN)的*/
=

知道每个变量表示哪段的区间和之后就可以很快的求出前缀和了; 要求sum(i) = a[1]+...+a[i]; c[i]表示a[i-lowbit(i)+1]+...+a[i]; 转化为子问题 还需要求a[1]+...+a[i-lowbit(i)];
了!

于是可以直接用一个循环求得sum,时间复杂度 为O(logN)
修改modify
树状数组的运用

对于刚才的一题,每次修改与 询问都是对C数组做处理.空间 复杂度为N,时间效率也有提高. 编程复杂度更是降了不少.
优 秀 的 算 法 !
树状数组的运用
Poj2352 Star
天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。 如果一个星星的左下方(包含正左和正下)有k颗星星,就说这颗星星 是k级的. 比如,在下面的例图中,星星5是3级的(1,2,4在它左下)。 星星2,4是1级的。例图中有1个0级,2个1级,1个2级,1个3级的星。

Just do it: 简单:

中等


POJ 2299 Ultra-QuickSort POJ 2352 Stars POJ 1195 Mobile phones

难题:

POJ 2155 Matrix POJ 3321 Apple Tree POJ 1990 MooFest POJ 2464 Brownie Points II
合集下载

树状数组及其应用

树状数组及其应用

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

树状数组离线技巧

树状数组离线技巧

树状数组离线技巧(原创实用版3篇)目录(篇1)1.树状数组的基本概念2.树状数组的离线算法3.离线算法的优化4.离线算法的应用正文(篇1)树状数组是一种基于数组的统计数据方法,可以快速计算多个等差数列的和。

其基本概念包括数组、计数器和递减性。

离线算法是树状数组的一种优化算法,可以提高计算效率。

离线算法的核心思想是利用二进制表示和压缩存储来减少空间占用。

1.树状数组的基本概念树状数组是一种基于数组的统计数据方法,可以快速计算多个等差数列的和。

其基本概念包括数组、计数器和递减性。

树状数组可以通过在数组中插入元素来计算多个等差数列的和,同时还可以维护计数器和递减性。

2.树状数组的离线算法离线算法是树状数组的一种优化算法,可以提高计算效率。

离线算法的核心思想是利用二进制表示和压缩存储来减少空间占用。

具体来说,离线算法将树状数组中的每个节点都表示为一个二进制数,并将计数器压缩存储为一个数组。

这样,就可以减少空间占用,提高计算效率。

3.离线算法的优化离线算法的优化包括利用二进制表示和压缩存储来减少空间占用,以及利用计数器的稀疏性来减少计算量。

二进制表示可以将树状数组中的每个节点都表示为一个二进制数,从而减少空间占用。

压缩存储可以将计数器压缩存储为一个数组,从而减少空间占用。

计数器的稀疏性是指树状数组中大多数节点的计数器值较小,可以利用这个特性来减少计算量。

4.离线算法的应用离线算法可以应用于很多实际问题中,比如搜索和查询、快速加法和去重等。

离线算法可以利用二进制表示和压缩存储来减少空间占用,从而提高计算效率。

目录(篇2)1.树状数组的基本概念2.树状数组的离线算法3.离线算法的优化4.离线算法的应用正文(篇2)树状数组是一种基于数组的统计数据集的方法,可以快速计算给定区间的和。

树状数组的主要优点是可以有效地减少计算量,提高计算速度。

下面我们来介绍树状数组的离线算法及其优化和应用。

一、树状数组的基本概念树状数组是一种基于数组的统计数据集的方法,可以快速计算给定区间的和。

树状数组详解

树状数组详解

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

树状数组

树状数组

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++树状数组讲解

树状数组(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、整体二分和莫队)数据结构离线优化树状数组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]。

树状数组(IndexTree)计算LIS举例--java实现

树状数组(IndexTree)计算LIS举例--java实现举例计算LIS.给定序列:2 5 3 7 3 1 9步骤根据给定序列个数,初始化树状数组.给定的元素需要在⼆叉树的叶⼦节点上放置.所以树状数组的叶⼦节点必须不少于元素个数.因为是⼆叉树,所以树状数组的⼤⼩必须是2^k.所以最终初始化⼤⼩的时候,只要选择第⼀个⽐序列长度⼤的2^k即可.树状数组是这样的:使⽤1节点的值表⽰4~7范围的值使⽤2节点的值表⽰4~5范围的值使⽤3节点的值表⽰6~7范围的值如果要得到57的值,需要5本⾝的值+67(也就是3)的值获取值应该就是以上⼏种情况对序列进⾏排序.最长上升排序规则: 根据元素从⼩到⼤(因为要递增),如果元素相同,索引⼤的在前⾯(因为是严格递增)最长不下降排序规则: 根据元素从⼩到⼤(因为要递增),如果元素相同,索引⼩的在前⾯(因为是不严格递增)最长下降排序规则: 根据元素从⼤到⼩(因为要递减),如果元素相同,索引⼤的在前⾯(因为是严格递减)最长不上升序规则: 根据元素从⼤到⼩(因为要递减),如果元素相同,索引⼩的在前⾯(因为是不严格递减)根据排序后的序列,按照顺序放⼊树状数组中.在往树状数组中放置的时候,由两个操作.a.查询在当前位置之前,有⼏个⽐当前放置的值要⼤的元素的个数---query(fromIdx,toIdx)b.更新当前位置的值,直到当前位置,最长 上升⼦序列的长度---update(index,value)画图举例⾸先将给定序列进⾏排序第⼀⾏和第⼆⾏代表原始序列的索引和值,sort后的两⾏代表排序后的索引和值.可以看到,关于相同的3,index⼤的排列到前⾯,index⼩的排列到后⾯.然后将它们放⼊树状数组中.(不要在意很丑的⽂字...)我这⾥按照不同颜⾊来表⽰不同的放置.颜⾊相同的箭头代表了树状数组被哪⼀次放置更新了.(实际上我不会做动态播放的图⽚ :weiqv: )放置次序就是根据上⾯的排序后数组放置的.(这⾥就根据红⾊字,放置次序来看放的顺序吧)a.放1的时候(放置次序1),在13号格⼦,前⾯都未放置其它值,所以13,6,3,1都被更新为1.b.放2的时候(放置次序2),在8号格⼦,前⾯的格⼦都没有放置其它值(实际上也没有前⾯的节点了,它是第⼀个).所以8,4,2被更新为1.因为1号格⼦本⾝就是1,本次更新的值,没有⽐1格⼦的值⼤,所以不更新.c.放3的时候(放置次序3),在12号格⼦(这时候按照索引从⼤到⼩排序就有⽤了),因为12号格⼦之前8号格⼦已经放了1,这代表我放它的时候,前⾯有⼀个数字⽐我⼩,于是当前的12号格⼦,需要放2(算上我本⾝,上升序列长度就是2了).于是6号节点需要从1变为2. 3,1节点也被更新为2.d.放3的时候(放置次序4),在10号格⼦.和c⼀样,因为10号格⼦之前8号格⼦放了1,所以10号格⼦需要放2,依次更新⽗节点,均变为2.e.放5的时候(放置次序5),在9号格⼦.仍然和c⼀样,将9号格⼦值变为2,同时更新⽗节点4号格⼦.因为2号格⼦值本⾝就是2,所以不需要更新.f.放7的时候(放置次序6),在11号格⼦.因为11号之前8,9,10都放了数值,我们需要在它们最⼤的基础上增加1,表⽰最⼤序列增加了⼀个.所以需要放3.同时更新所有⽗节点.g.放9的时候(放置次序7),在14号格⼦,和f⼀样,查询出8~13号格⼦的数值的最⼤值,在此基础上增加1表⽰最⼤序列增加了⼀个.所以需要放置4.同时更新所有⽗节点.h.现在放置完毕,我们要找例⼦中数组的最长上升序列,只需要去查找1号格⼦值就可以啦.答案为4. :taikaixin:根据步骤描述,就需要之前说的query和update⽅法了.query⽅法查询当前位置之前的范围区间的值(这⾥是查找最⼤值,也可以⽤于其它的区间相关的统计),update⽅法⽤来更新本⾝和⽗节点的值.根据⼀道模板题⽬,加深对它的理解吧.主要就是树状数组关键的两个⽅法.Stock ExchangeTime Limit: 1000MSTotal Submissions: 11485DescriptionThe world financial crisis is quite a subject. Some people are more relaxed while others are quite anxious. John is one of them. He is very concerned about the evolution of the stock exchange. He follows stock prices every day looking for rising trends. Given a sequence of numbers p1, p2,...,pn representing stock prices, a rising trend is a subsequence pi1 < pi2 < ... < pik, with i1 < i2 < ... < ik. John’s problem is to find very quickly the longest rising trend.InputEach data set in the file stands for a particular set of stock prices. A data set starts with the length L (L ≤ 100000) of the sequence of numbers, followed by the numbers (a number fits a long integer).White spaces can occur freely in the input. The input data are correct and terminate with an end of file.OutputThe program prints the length of the longest rising trend.For each set of data the program prints the result to the standard output from the beginning of a line.Sample Input65 2 1 4 5 331 1 144 3 2 1Sample Output311HintThere are three data sets. In the first case, the length L of the sequence is 6. The sequence is 5, 2, 1, 4, 5, 3. The result for the data set is the length of the longest rising trend: 3.SourceSoutheastern European Regional Programming Contest 2008以下是代码:import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.Arrays;import parator;import java.util.Scanner;import java.util.StringTokenizer;public class Main {static int n,len;static int[] lis;public static void main(String[] args) throws Exception {Scanner sc = new Scanner(System.in);while (sc.hasNextInt()){n = sc.nextInt();len = 1;while (len<n){len*=2;}lis = new int[len*2];int[][] data = new int[n][2];for(int i = 0;i<n;i++){data[i] = new int[]{sc.nextInt(),i};}Arrays.sort(data, new Comparator<int[]>() {@Overridepublic int compare(int[] o1, int[] o2) {if(o1[0] == o2[0]){return o2[1]-o1[1];}return o1[0]-o2[0];}});for(int[] s:data){int preCnt = query(len,len+s[1]-1);update(s[1]+len,preCnt+1);}System.out.println(query(len,len+n-1));}}private static int query(int start,int end){int result = 0;while (start<=end){if(start%2 == 1){result = Math.max(lis[start],result);}if(end%2 == 0){result = Math.max(lis[end],result);}start = (start+1)/2;end = (end-1)/2;}return result;}private static void update(int index,int val){ while (index>0 && val>lis[index]){lis[index] = val;index/=2;}}}。

树状数组

树状数组是一个查询和修改复杂度都为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)的祖先。

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