树状数组学习心得

来源:互联网 发布:蘑菇街和淘宝 编辑:程序博客网 时间:2024/05/22 08:23

文章来自:http://www.cppblog.com/linyangfei/archive/2008/09/24/62688.html 

 

POJ 3321 Apple Tree

http://acm.pku.edu.cn/JudgeOnline/problem?id=3321

POJ 2481 Cows

http://acm.pku.edu.cn/JudgeOnline/problem?id=2481

POJ 2155 Matrix

http://acm.pku.edu.cn/JudgeOnline/problem?id=2155

       今天在POJ淘了这几道题目,学习了一下树状数组的用法,跟大家分享一下心得。可以把树状数组看成一种数据结构,对于一个数组,如果有多次操作,每次的操作有两种:1、修改数组中某一元素的值,2、求和,求数组元素a[1]+a[2]+…a[num]的和。这是树状数组最基本的应用了,用树状数组可以实现O(log(n))的修改以及O(log(n))的求和。当然用线段树完全可以胜任这些计算,但是线段树写起来代码比较长,并且线段树要占用2*n大小的空间。下面先给出树状数组的三个基本操作的函数:

int lowbit(int k)

{

       return k&(-k);

}

//lowbit函数是计算k的二进制位最低位不为0的数字的权值。

void Modify(int num, int v)

{

       while(num <= n)

       {

              c[num]+=v;

              num+=lowbit(num);

       }

}

//Modify函数是往数组c中修改值,更新整个数组的值,实现了操作1

int Sum(int num)

{

       int ans=0;

       while(num > 0)

       {

              ans+=c[num];

              num-=lowbit(num);

       }

       return ans;

}

//Sum函数返回数组元素a[1]+a[2]+…a[num]的和,实现操作2

       树状数组的巧妙之处在于对于数组下标的二进制的操作,假设a[1...N]为原数组,定义c[1...N]为对应的树状数组:

c[i] = a[i - 2^k + 1] + a[i - 2^k + 2] + ... + a[i]

其中ki的二进制表示末尾0的个数,所以2^k即为i的二进制表示的最后一个1的权值.

可以把树状数组看作是把数组分成了若干个2^k大小的空间。对于一个下标ic[i]的值是由i/(lowbit(i))个数组元素的值所组成的,每次步进的单位是k=lowbit(i),这个有点像二分归并的思想!这样就可以实现O(log(n))的修改和查询。

       下面是树状数组的具体应用:

       3321 Apple Tree 一棵树上长了苹果,每一个树枝节点上有长苹果和不长苹果两种状态,两种操作,一种操作能够改变树枝上苹果的状态,另一种操作询问某一树枝节点一下的所有的苹果有多少。具体做法是做一次dfs,记下每个节点的开始时间low[i]和结束时间high[i],那么对于i节点的所有子孙的开始时间和结束时间都应位于low[i]high[i]之间,另外用一个数组c[i]记录附加在节点i上的苹果的个数,然后用树状数组统计low[i]high[i]之间的附加苹果总数。这里用树状数组统计区间可以用Sum(high[i])-Sum(low[i]-1)来计算。

 


 1#include <stdio.h>
 2#include <string.h>
 3#include <vector>
 4using namespace std;
 5
 6//vector<int> g[100005];
 7struct Node
 8{
 9    int v;
10    struct Node *next;
11}
g[100005];
12int n,m,cnt,low[100005],high[100005],c[100005],flag[100005];
13bool mark[100005];
14
15void dfs(int v)
16{
17    struct Node *p=g[v].next;
18    mark[v]=true;
19    cnt++;
20    low[v]=cnt;
21    while(p)
22    {
23        if(!mark[p->v])
24            dfs(p->v);
25        p=p->next;
26    }

27    high[v]=cnt;
28}

29int lowbit(int k)
30{
31    return k&(-k);
32}

33void Modify(int num, int v)
34{
35    while(num <= n)
36    {
37        c[num]+=v;
38        num+=lowbit(num);
39    }

40}

41int Sum(int num)
42{
43    int ans=0;
44    while(num > 0)
45    {
46        ans+=c[num];
47        num-=lowbit(num);
48    }

49    return ans;
50}

51
52int main()
53{
54    int i,j,a,b,ans;
55    char temp[10];
56    struct Node *p;
57    //freopen("in.txt","r",stdin);
58    scanf("%d",&n);
59    memset(g,0,sizeof(g));
60    for(i=1; i<n; i++)
61    {
62        scanf("%d%d",&a,&b);
63        p=new Node;
64        p->next=g[a].next;
65        p->v=b;
66        g[a].next=p;
67        p=new Node;
68        p->next=g[b].next;
69        p->v=a;
70        g[b].next=p;
71    }

72    memset(mark,false,sizeof(mark));
73    memset(c,0,sizeof(c));
74    for(i=1; i<=n; i++)
75        flag[i]=1;
76    cnt=0;
77    dfs(1);
78    scanf("%d",&m);
79    while(m--)
80    {
81        scanf("%s",temp);
82        if(temp[0== 'Q')
83        {
84            scanf("%d",&a);
85            ans=high[a]-low[a]+1+Sum(high[a])-Sum(low[a]-1);
86            printf("%d/n",ans);
87        }

88        else
89        {
90            scanf("%d",&a);
91            if(flag[a]) Modify(low[a],-1);
92            else Modify(low[a],1);
93            flag[a]^=1;
94        }

95    }

96    return 0;
97}

98
99

 

       2481 Cows n个区间[Si,Ei],区间[Sj,Ej]< [Si,Ei] Si <= Sj and Ej <= Ei and Ei - Si > Ej – Sj。按y坐标从小到达,x坐标从大到小的顺序排序,然后从后往前扫描,记录i之前所有的j区间Sj<Si的个数,这个用树状数组实现。扫描一遍可得出结果。

 


 1#include <stdio.h>
 2#include <string.h>
 3#include <algorithm>
 4using namespace std;
 5
 6struct P
 7{
 8    int x,y,id;
 9}
p[100005];
10int n,a[100005],max_n,b[100005];
11
12int lowbit(int k)
13{
14    return k&(-k);
15}

16void Modify(int num, int v)
17{
18    while(num <= max_n)
19    {
20        a[num]+=v;
21        num+=lowbit(num);
22    }

23}

24int Sum(int num)
25{
26    int ans=0;
27    if(num <= 0return 0;
28    while(num)
29    {
30        ans+=a[num];
31        num-=lowbit(num);
32    }

33    return ans;
34}

35bool operator <(const P a, const P b)
36{
37    if(a.y == b.y) return a.x > b.x;
38    return a.y < b.y;
39}

40
41int main()
42{
43    int i;
44    //freopen("in.txt","r",stdin);
45    while(scanf("%d",&n), n)
46    {
47        max_n=0;
48        for(i=0; i<n; i++)
49        {
50            scanf("%d%d",&p[i].x,&p[i].y);
51            p[i].id=i;
52            p[i].x++;
53            p[i].y++;
54            if(p[i].y > max_n) max_n=p[i].y;
55        }

56        sort(p,p+n);
57        memset(a,0,sizeof(a));
58        for(i=n-1; i>=0; i--)
59        {
60            if(i != n-1 && p[i].y == p[i+1].y && p[i].x == p[i+1].x)
61                b[p[i].id]=b[p[i+1].id];
62            else
63                b[p[i].id]=Sum(p[i].x);
64            Modify(p[i].x,1);
65        }

66        for(i=0; i<n; i++)
67        {
68            if(i) printf(" ");
69            printf("%d",b[i]);
70        }

71        printf("/n");
72    }

73    return 0;
74}

75
76

 

       2155 Matrix n*n01矩阵,两种操作,1、翻转矩形(x1,y1(x2,y2)的值,2、输出位置为(x,y)矩阵处的值。先考虑一维的情况,设A<B,那么要翻转[A,B]之间的值,可以分解为两步操作,先翻转[1,A-1],然后再翻转[1,B],其中翻转的次数就可以用树状数组来计算。然后再将次操作扩展到二维的情形,只需将x方向与y方向套成一个二重循环即可。从这里我们也可以看到树状数组处理类似问题时比线段树的优越性。从代码的长度,空间消耗上面,树状数组都有明显的优势。

原创粉丝点击