树状数组

我们知道要是一个数组的和C[i]=A[1]+A[2]+.......+A[i],那么我更改其中任意一项值A[i],那么C[i],C[i+1],C[i+2]....C[n]都会随之变化,所以我们要调整C[]当n非常大时就需要很长的时间,必然会导致超时。这样就可以引入树状数组,它的修改和求和的复杂度为nlogn.

树状数组是一种数组结构,能够高效地获取数组中连续n个数的和。

下面这张图是基本上大多数博客都有的一张图,主要为了让我们更容易理解树状数组。

红色的C[i]表示树状数组,且C[i] = A[i–2^k+ 1] +A[i-2^k+2] … + A[i] (i>=1)

其中,k为i在二进制下末尾0的个数,(可以利用位运算2^k=i&(i^(i-1))=i&(-i)),则我们称C为树状数组。由树状数组的定义可知:

C[1]=A[1];   C[2]=A[1]+A[2]=C[1]+A[2];  C[3]=A[3]; C[4]=A[1]+A[2]+A[3]+A[4]=C[2]+C[3]+A[4];

C[5]=A[5]; C[6]=A[5]+A[6]=C[5]+C[6]; C[7]=A[7]; C[8]=...=C[4]+C[6]+C[7]+A[8];

由图可知,k为这颗树的高度,且最高不会超过logn,这个时候我们改变A[i]的值后可以由C[i]往根节点上溯,调整这条路上的C[]即可。比方说改变A[1],即pos=1的值,需要更新的值为C[1],C[2],C[4],C[8],位置变化的规律为:pos+=LowBit(pos),复杂度为logn。模板为:

  1. #define LowBit(num) num&(-num)  
  2. void Add(int pos,int value)  
  3. {   for(int i=pos;i<MAX;i+=LowBit(i))  
  4.         C[i]+=value;  
  5. }  

要求统计某一个位置num的前num项和时:只要找到num前的所有的最大子树,然后把其根节点的C[]加起来即可。如:要求前5项的和,用Sum(5)表示,则Sum(5)=C[5]+C[4],需要加起来的C[i]中的i也有规律可循,i-=LowBit(i),模板为:

  1. int Add(int num)  
  2. {   int sum=0;  
  3.     for(int i=num;i>0;i-=LowBit(i))  
  4.         sum+=C[i];  
  5.     return sum;      
  6. }  

树状数组能快速求任意区间的和:A[i] + A[i+1] + … + A[j],设sum(k) = A[1]+A[2]+…+A[k],则A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。

用法1:树状数组---插点问线,当只有某一点的数值变化,而查找的区间为一段时用这个方法。

直接上题目吧,例题1:HDU 1166(敌兵布阵)直接用暴力的话肯定会超时。

  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. using namespace std;  
  5. const int MAX=1000010;  
  6. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  7. int n,total,value,C[MAX];  
  8. char Find[10];  
  9. int LowBit(int num)//二进制后面为0的个数  
  10. {   return num&(-num);  
  11. }  
  12. void Add(int pos,int val)//将pos位置的值+val  
  13. {   while(pos<=n)  
  14.     {   C[pos]+=val;//则每个位置的C[]都要改变,看下图有哪些C[]改变了,比方我把A[4]+val,变化的C[]有C[4],C[8]..变化,C[5],C[6]..并未变化  
  15.         pos+=LowBit(pos);//求出下一个要改变的位置,回溯到根节点  
  16.     }  
  17. }  
  18. int Sum(int num)//求前num项的和  
  19. {   int sum=0;  
  20.     while(num>0)  
  21.     {   sum+=C[num];  
  22.         num-=LowBit(num);//找到num前的所有最大子树  
  23.     }  
  24.     return sum;  
  25. }  
  26. int main()  
  27. {   int sum=1;  
  28.     scanf("%d",&total);  
  29.     while(total--)  
  30.     {   CLR(C,0);//要记得初始化   
  31.         printf("Case %d:\n",sum++);   
  32.         scanf("%d",&n);  
  33.         for(int i=1;i<=n;i++)//要从1开始!!!   
  34.         {   scanf("%d",&value);  
  35.             Add(i,value);  
  36.         }  
  37.             
  38.         while(scanf("%s",Find))  
  39.         {   int End,Start;  
  40.             if(Find[0]=='E'break;  
  41.             scanf("%d%d",&Start,&End);   
  42.             if(Find[0]=='A') Add(Start,End);  
  43.             else if(Find[0]=='S') Add(Start,-End);  
  44.             else printf("%d\n",Sum(End)-Sum(Start-1));   
  45.         }  
  46.     }  
  47.     return 0;  
  48. }  

和这个题目一样的是:NYOJ 116(士兵杀敌)~~~~~~
用法2:插线问点,当有某一段的数值都有变化时,而查询只有一个位置时用插线问点。

  1. #include<iostream>  
  2. #include<cstring>  
  3. #include<cstdio>  
  4. using namespace std;  
  5. const int MAX=1000010;  
  6. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  7. int n,m,num,C[MAX];  
  8. char Find[10];  
  9. int LowBit(int num)  
  10. {   return num&(-num);  
  11. }  
  12. void Add(int pos,int val)//前n项每项增加val   
  13. {   while(pos>0)  
  14.     {   C[pos]+=val;  
  15.         pos-=LowBit(pos);      
  16.     }  
  17. }  
  18. int Sum(int pos)  
  19. {   int sum=0;  
  20.     while(pos<=n)  
  21.     {   sum+=C[pos];  
  22.         pos+=LowBit(pos);  
  23.     }  
  24.     return sum;  
  25. }  
  26. int main()  
  27. {   CLR(C,0);  
  28.     scanf("%d%d",&n,&m);  
  29.     for(int i=1;i<=n;i++)  
  30.     {   scanf("%s",Find);  
  31.         if(Find[0]=='A')  
  32.         {   int Start,End;  
  33.             scanf("%d%d%d",&Start,&End,&num);  
  34.             /****下面两行Nice****/  
  35.             Add(Start-1,-num);  
  36.             Add(End,num);  
  37.             /********************/  
  38.         }  
  39.         else   
  40.         {   scanf("%d",&num);  
  41.             printf("%d\n",Sum(num));  
  42.         }  
  43.     }  
  44.     return 0;  
  45. }  

用法3:多维树状数组,增加模板:

  1. #define LowBit(num) num&(-num)  
  2. void Add(int x,int y,....,int val)  
  3. {   for(int i=x;i<MAX;i+=LowBit(i))  
  4.         for(int j=y;j<MAX;j+=LowBit(j))  
  5.             .......//是几维的就几层  
  6.             C[i][j][...]+=val;  
  7. }  
  8. void Sum(int x,int y,.....)  
  9. {   int sum=0;  
  10.     for(int i=x;i>0;i-=LowBit(i))  
  11.         for(int j=y;j>0;j-=LowBit(j))  
  12.             ......  
  13.             sum+=C[i][j][...];  
  14.     return sum;          
  15. }  

例题3:HDU 1892(See you~~),代码:

  1. #include<iostream>  
  2. #include<cstring>  
  3. #include<cstdio>  
  4. using namespace std;  
  5. const int MAX=1010;  
  6. #define min(a,b) a<b?a:b   
  7. #define LowBit(num) num&(-num)  
  8. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  9. int total,n,Fx,Fy,Tx,Ty,value,sum,Count=1,C[MAX][MAX];  
  10. void Add(int x,int y,int val)  
  11. {   for(int i=x;i<MAX;i+=LowBit(i))  
  12.         for(int j=y;j<MAX;j+=LowBit(j))  
  13.             C[i][j]+=val;  
  14. }  
  15. int Sum(int x,int y)//求从位置(1,1)到(x,y)的元素之和  
  16. {   int sum=0;  
  17.     for(int i=x;i>0;i-=LowBit(i))  
  18.         for(int j=y;j>0;j-=LowBit(j))  
  19.             sum+=C[i][j];  
  20.     return sum;  
  21. }     
  22. int main()  
  23. {   scanf("%d",&total);  
  24.     while(total--)  
  25.     {   CLR(C,0);  
  26.         for(int i=1;i<MAX;i++)//记得要初始化为1   
  27.             for(int j=1;j<MAX;j++)  
  28.                 Add(i,j,1);  
  29.         printf("Case %d:\n",Count++);  
  30.         scanf("%d",&n);  
  31.         for(int i=0;i<n;i++)  
  32.         {   char c[5];  
  33.             scanf("%s",c);  
  34.             switch(c[0])  
  35.             {   case 'A':  
  36.                     scanf("%d%d%d",&Fx,&Fy,&value);//在(Fx,Fy)处增加value本书   
  37.                     Add(Fx+1,Fy+1,value);  
  38.                     break;  
  39.                 case 'D':  
  40.                     scanf("%d%d%d",&Fx,&Fy,&value);//在(Fx,Fy)处移除value本书,如果这里的书不足,则只移除原有的书  
  41.                     sum=Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);//求出(Fx+1,Fy+1)位置的书的数目,可以用容斥定理解决  
  42.                     value=min(value,sum);  
  43.                     Add(Fx+1,Fy+1,-value);   
  44.                     break;  
  45.                 case 'M':  
  46.                     scanf("%d%d%d%d%d",&Fx,&Fy,&Tx,&Ty,&value);//从(Fx,Fy)处移value本书到(Tx,Ty)处,同上面不足的话移除所有的书   
  47.                     sum=Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);  
  48.                     value=min(sum,value);  
  49.                     Add(Fx+1,Fy+1,-value);  
  50.                     Add(Tx+1,Ty+1,value);  
  51.                     break;  
  52.                 default:     
  53.                     scanf("%d%d%d%d",&Fx,&Fy,&Tx,&Ty);  
  54.                     if(Fx>Tx) swap(Fx,Tx);  
  55.                     if(Fy>Ty) swap(Fy,Ty);  
  56.                     sum=Sum(Tx+1,Ty+1)+Sum(Fx,Fy)-Sum(Fx,Ty+1)-Sum(Tx+1,Fy);    
  57.                     printf("%d\n",sum);          
  58.             }  
  59.         }  
  60.     }  
  61.     return 0;  
  62. }  

求点(Fx+1,Fy+1)的书的数目:

可知:由面积关系可知,B点的值为:Sum(Fx+1,Fy+1)+Sum(Fx,Fy)-Sum(Fx+1,Fy)-Sum(Fx,Fy+1);

最后的求(Fx,Fy)到(Tx+1,Ty+1)的元素之和同样可求,即求ABCD的面积,只是C(Fx,Fy),B(Tx+1,Ty+1),A(Fx,Ty+1),D(,Tx+1,Fy),这个时候面积为:

Sum(Tx+1,Ty+1)+Sum(Fx,Fy)-Sum(Fx,Ty+1)-Sum(Tx+1,Fy)

三维树状数组:关键是点的值的变化,如下图,只标了几个点的坐标,O不一定指原点,只是为了立体感强点。

例题4:HDU 3584(Cube)

 

  1. #include<iostream>  
  2. #include<cstring>  
  3. #include<cstdio>  
  4. using namespace std;  
  5. const int MAX=110;  
  6. #define LowBit(num) num&(-num)  
  7. #define CLR(arr,val) memset(arr,val,sizeof(arr))  
  8. int n,m,C[MAX][MAX][MAX];   
  9. void Add(int x,int y,int z,int val)  
  10. {   for(int i=x;i<=n;i+=LowBit(i))  
  11.         for(int j=y;j<=n;j+=LowBit(j))  
  12.             for(int k=z;k<=n;k+=LowBit(k))  
  13.                 C[i][j][k]+=val;   
  14. }  
  15. int Sum(int x,int y,int z)  
  16. {   int sum=0;  
  17.     for(int i=x;i>0;i-=LowBit(i))  
  18.         for(int j=y;j>0;j-=LowBit(j))  
  19.             for(int k=z;k>0;k-=LowBit(k))  
  20.                 sum+=C[i][j][k];  
  21.     return sum;               
  22. }  
  23. int main()  
  24. {   while(scanf("%d%d",&n,&m)!=EOF)  
  25.     {   CLR(C,0);  
  26.         for(int i=0;i<m;i++)  
  27.         {   int comp,Sx,Sy,Sz,Ex,Ey,Ez;  
  28.             scanf("%d",&comp);  
  29.             switch(comp)  
  30.             {   case 0:  
  31.                     scanf("%d%d%d",&Sx,&Sy,&Sz);  
  32.                     printf("%d\n",Sum(Sx,Sy,Sz)&1);  
  33.                     break;  
  34.                 default:  
  35.                     scanf("%d%d%d%d%d%d",&Sx,&Sy,&Sz,&Ex,&Ey,&Ez);  
  36.                     //变成立方体了~~~~   
  37.                     Add(Sx,Sy,Sz,1);    
  38.                     Add(Sx,Ey+1,Ez+1,1);  
  39.                     Add(Ex+1,Sy,Ez+1,1);  
  40.                     Add(Ex+1,Ey+1,Sz,1);  
  41.                     Add(Ex+1,Sy,Sz,1);  
  42.                     Add(Sx,Ey+1,Sz,1);  
  43.                     Add(Sx,Sy,Ez+1,1);  
  44.                     Add(Ex+1,Ey+1,Ez+1,1);     
  45.             }  
  46.         }  
  47.     }  
  48.     return 0;  
  49. }  
涉及到树状数组的题目有: HDOJ 1116,1394,1541,1556,1640,1867,2227,,2275,2430,2492,2642,2668,2689,2838,2852,3047,3450,3743,3887,4031, POJ1195,1990,2029,2155,2299,2352,2464,2481,3067,3321,3468,

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值