树状数组

合集下载

树状数组及其应用

树状数组及其应用

树状数组及其应用(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++树状数组讲解

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]。

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

树状数组(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)的祖先。

树状数组

树状数组

树 状
围内的数据之和.
修改一个位置上数字的值,就是修改一个 叶子结点的值,而当程序由叶子结点返回
数 组
根节点的同时顺便修改掉路径上的结点的 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. 总结树状数组是一种高效的数据结构,适用于解决多种问题,包括维护前缀和、区间和以及维护区间最值等。

数据结构之树状数组

数据结构之树状数组

如果,我们要分析的是“改段求点”型,即
修改某一个区间,使其每个值都加上值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。

树状数组是一个查询和修改复杂度都为logn级别的区间统计

树状数组是一个查询和修改复杂度都为logn级别的区间统计

树状数组是一个查询和修改复杂度都为log(n)级别的区间统计的数据结构,在思想上类似于线段树。

相比线段树,树状数组需要的空间较少,编程复杂度也较低,但适用范围比线段树小。

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

令每个结点的值为这棵树的值的总和,那么容易发现:C1 = A1C2 = A1 + A2C3 = A3C4 = 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));}利用机器补码的特点,这个函数可以改得更方便int lowbit(int i){return i&(-i);}如果要把a[n]增加m,可以通过调用如下函数实现void add(int i,int v){while (i<=n){a[i]+=v;i+=lowbit(i);}}如果要统计a[1]到a[n]之间的和,可以通过调用如下函数实现int sum(int i){int s=0;while (i>0){s+=a[i];i-=lowbit(i);}return s;}这是一维的情况,很容易能推广到二维。

POJ上用到树状数组的题目:poj2352 Starspoj2481 Cowspoj1195 Mobile phonespoj3067 Japanpoj2155 Matrixpoj2464 Brownie Points II(以下是经典)树状数组是一种非常优雅的数据结构.当要频繁的对数组元素进行修改,同时又要频繁的查询数组内任一区间元素之和的时候,可以考虑使用树状数组.最直接的算法可以在O(1)时间内完成一次修改,但是需要O(n)时间来进行一次查询.而树状数组的修改和查询均可在O(log(n))的时间内完成.假设a[1...N]为原数组,定义c[1...N]为对应的树状数组:c[i] = a[i - 2^k + 1] + a[i - 2^k + 2] + ... + a[i]其中k为i的二进制表示末尾0的个数,所以2^k即为i的二进制表示的最后一个1的权值. 所以2^k可以表示为n&(n^(n-1))或更简单的n&(-n):int lowbit(int n){return n& (-n);}对a[n]进行修改后,需要相应的修改c数组中的p1, p2, p3...等一系列元素其中p1= n,p i+1= p i+ lowbit(p i)所以修改原数组中的第n个元素可以实现为:void Modify(int n, int delta){while(n <= N){ c[n] += delta; n += lowbit(n);}}当要查询a[1],a[2]...a[n]的元素之和时,需要累加c数组中的q1, q2, q3...等一系列元素其中q1 = n,q i+1= q i - lowbit(q i)所以计算a[1] + a[2] + .. a[n]可以实现为:int Sum(int n){int result = 0;while(n != 0){ result += c[n]; n -= lowbit(n); }return result;}树状数组可以扩充到二维。

树状数组 详解

树状数组 详解

树状数组详解对于普通数组,其修改的时间复杂度位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.求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]的前缀和。

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



要想知道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
相关文档
最新文档