(转)树状数组
来源:互联网 发布:php网站开发实例 编辑:程序博客网 时间:2024/05/14 03:32
树状数组
引言
在解题过程中,我们有时需要维护一个数组的前缀和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
int Lowbit(int t) {
return t & ( t ^ ( t - 1 ) );
}
求前n项和
int Sum(int end)
{
int sum = 0;
while(end > 0)
{
sum += in[end];
end -= Lowbit(end);
}
return sum;
}
对某个元素进行加法操作
void plus(int pos , int num) {
while(pos <= n) {
in[pos] += num;
pos += Lowbit(pos);
}
}
修改原数组中的第n个元素可以实现为:
void Modify(int n, int delta)
{
while(n <= N)
{ c[n] += delta; n += lowbit(n);}
}
例题:
此题的难点在于将题目转化为求区间的和问题,也就是将子树的的和转化为区间的和,原先子树的节点的是不连续的,可以先构造成树,通过深搜遍历,给树的节点进行离散化,然后就可以用线段树或树状数组进行区间问题的求解。
#include
#include
#include
#define lowbit(x) (x&(x^(x-1)))
using namespace std;
struct Node
{
Node(const int& i)
{
v = i; link = NULL;
}
Node()
{
link = NULL;
}
int v;
Node* link;
};
Node map[100001];
int begin[100001],end[100001],app_num[100001],depth;
bool visited[100001],apple[100001];
void init(const int& n)
{
for( int i = 1; i <= n; ++ i )
{
map[i].v = i; map[i].link = NULL;
visited[i] = 0;
apple[i] = 1; app_num[i] = 0;
}
}
void add_edge(int i,int j)
{
Node *p = new Node(i);
Node *q = new Node(j);
Node *temp = &map[i];
while( temp->link != NULL ) temp = temp->link;
temp->link = q;
temp = &map[j];
while( temp->link != NULL ) temp = temp->link;
temp->link = p;
}
void DFS(int i) // 对树重新排序
{
int be = ++depth;
Node *p = map[i].link;
visited[i] = 1;
while( p != NULL )
{
if( ! visited[p->v] ) DFS(p->v);
p = p->link;
}
begin[i] = be;
end[i] = depth;
}
void modif(int k,int d,int n)
{
int p = k;
while( p <= n )
{
app_num[p] += d;
p += lowbit(p);
}
}
int query(int k)
{
int p = k;
int sum = 0;
while( p > 0 )
{
sum += app_num[p];
p -= lowbit(p);
}
return sum;
}
int main()
{
int n,m,i,j,u,v;
char ch;
scanf("%d",&n);
init(n);
for(i = 1; i < n; ++ i )
{
scanf("%d%d",&u,&v);
add_edge(u,v);
}
DFS(1);
for( i = 1; i <= n; ++ i )
modif(i,1,n);
scanf("%d",&m);
while( m-- )
{
getchar();
scanf("%c%d",&ch,&i);
if( ch == 'C' )
{
if( apple[i] ) modif(begin[i],-1,n);
else modif(begin[i],1,n);
apple[i] = !apple[i];
}
else printf("%d/n",query(end[i])-query(begin[i]-1));
}
return 0;
}
参考文献
- 吴豪,树状数组
- (转)树状数组
- (转)树状数组
- 树状数组详解(转)
- 树状数组学习(转)
- 树状数组入门(转)
- 树状数组 详细解说(转)
- 树状数组介绍(转)
- [转]树状数组
- 树状数组--转
- 树状数组(转载)
- Stars(树状数组)
- 树状数组(interval)
- 树状数组(2)
- 树状数组(3)
- 树状数组(4)
- 树状数组(5)
- 树状数组(6)
- poj2352Stars(树状数组)
- 全文检索的方式
- 三段感人小故事
- myeclipse的Debug问题。
- ZOJ 1115
- 写给当代大学生的18条建议
- (转)树状数组
- 二十四条成熟法则,你符合几条?
- Java中Split函数的用法技巧
- struts2 东拼西凑
- 心句
- 关于getchar()函数
- lesson1
- linux 下的挂载
- 对QQ和搜狗拼音的期望 - 2010.6