• 算法---树状数组


    一、树状数组的概念

    树状数组是利用了数的二进制特征进行检索的一种树状结构。关于树状数组的结构图可以看下这篇文章,这位博主的结构图很详细(树状数组结构图)那么树状数组我们是用来干什么的呢?其实它和前缀和与差分也是也是一样的,也是用来求解关于区间问题的,具体我们可以看看后面的代码展现来感受一下就知道了。

    我们定义一个树状数组tree[],原始数组a[]

    由图的结构我们可以知道tree[1]=a1

    tree[2]=tree[1]+a2

    tree[3]=a3

    tree[4]=tree[2]+tree[3]+a4

    .....

    tree[8]=tree[4]+tree[6]+tree[7]+a8

    我们会发现一个问题,该如何求出tree呢?这就需要在每次的二进制后面加上一,找到下一个数了。这就需要用到lowbit(x)=x&(-x)来找到二进制中的最后一个一了。

    如:

    1. x x二进制 lowbit(x) tree
    2. 1 1 1 tree[1]=a1
    3. 2 10 2 tree[2]=a1+a2
    4. 3 11 1 tree[3]=a3
    5. 4 100 4 tree[4]=a4+a3+a2+a1
    6. 5 101 1 tree[5]=a5
    7. 6 110 2 tree[6]=a6+a5

    相信规律很明显我就不细说了吧。 

    二、树状数组的代码实现

    (1)单点修改+区间查询 

    1. #include
    2. using namespace std;
    3. #define lowbit(x)((x)&-(x))
    4. const int N=1e3+10;
    5. int a[N];
    6. int tree[N];
    7. int n;
    8. void up(int x,int d)
    9. {
    10. while(x<=N){
    11. tree[x]+=d;
    12. x+=lowbit(x);
    13. }
    14. }
    15. int sum(int x)
    16. {
    17. int ans=0;
    18. while(x>0){
    19. ans+=tree[x];
    20. x-=lowbit(x);
    21. }
    22. return ans;
    23. }
    24. int main()
    25. {
    26. cin>>n;
    27. for(int i=1;i<=n;i++){
    28. cin>>a[i];
    29. up(i,a[i]);
    30. }
    31. cout<<sum(6)-sum(3)<//查看区间[4,6]的和
    32. //现在给a[5]+100
    33. up(5,100);
    34. cout<<sum(6)-sum(3)<
    35. return 0;
    36. }

    (2)区间修改+单点查询

    区间修改用到了前面我们学到的拆分知识,修改区间[L,R]的值,用差分,差分数组的变化时d[l]++,

    d[r+1]--;这里同样也是,具体的我们看下代码。

    注意点是什么呢,与前面的(1)相比较up的第二个参数第一个是a[i],得出来的sum就是a[i]的前缀和,而在(2)点中,up接受的第二个参数是a[i]-a[i-1],那么sum[i]计算的就是a[i],相当于sum[i]是tree[i]的前缀和,也就是说,第二个参数接受的是什么,sum[i]计算得到的就是第二个参数相加累计得到的。

    1. #include
    2. using namespace std;
    3. #define lowbit(x)((x)&-(x))
    4. const int N=1e3+10;
    5. int n,m;
    6. int a[N];
    7. int tree[N];
    8. void up(int x,int d)
    9. {
    10. while(x<=N){
    11. tree[x]+=d;
    12. x+=lowbit(x);
    13. }
    14. }
    15. int sum(int x)
    16. {
    17. int ans=0;
    18. while(x>0){
    19. ans+=tree[x];
    20. x-=lowbit(x);
    21. }
    22. return ans;
    23. }
    24. int main()
    25. {
    26. cin>>n>>m;
    27. int old=0;
    28. for(int i=1;i<=n;i++){
    29. cin>>a[i];
    30. up(i,a[i]-old);
    31. old=a[i];
    32. }
    33. while(m--){
    34. int l,r,q;
    35. cin>>l>>r>>q;
    36. up(l,q);
    37. up(r+1,-q);
    38. }
    39. //输出a中的所有元素
    40. for(int i=1;i<=n;i++){
    41. cout<<sum(i)<<" ";
    42. }
    43. return 0;
    44. }
    45. /*
    46. 5 3
    47. 1 2 3 4 5
    48. 1 2 3
    49. 1 1 5
    50. 3 5 1
    51. */

    (3)区间修改+区间查询

    区间查询与区间查询我们就需要两个树状数组了。

    原因是什么呢,看下推理公式就知道了。

    1. a1+a2+a3+....+ak
    2. =d1+(d1+d2)+(d1+d2+d3)+...+(d1+d2+d3+...+dk)
    3. =k*d1+(k-1)*d2+(k-2)*d3+...+dk
    4. =k(d1+d2+...+dk)-(d2+2*d3+...+(k-1)*dk)
    5. =d*(d1+...+dk)-(i-1)*di(i从1到k)

    那么我们创建的两个树状数组一个实现的是d[i],一个实现的是(i-1)*di

    代码展现:

    1. #include
    2. using namespace std;
    3. #define lowbit(x)((x)&-(x))
    4. const int N=1e3+10;
    5. int a[N];
    6. int tree1[N],tree2[N];
    7. int n,m;
    8. void up1(int x,int d)
    9. {
    10. while(x<=N){
    11. tree1[x]+=d;
    12. x+=lowbit(x);
    13. }
    14. }
    15. void up2(int x,int d)
    16. {
    17. while(x<=N){
    18. tree2[x]+=d;
    19. x+=lowbit(x);
    20. }
    21. }
    22. int sum1(int x)
    23. {
    24. int ans=0;
    25. while(x>0){
    26. ans+=tree1[x];
    27. x-=lowbit(x);
    28. }
    29. return ans;
    30. }
    31. int sum2(int x)
    32. {
    33. int ans=0;
    34. while(x>0){
    35. ans+=tree2[x];
    36. x-=lowbit(x);
    37. }
    38. return ans;
    39. }
    40. int main()
    41. {
    42. cin>>n>>m;
    43. int old=0;
    44. for(int i=1;i<=n;i++){
    45. cin>>a[i];
    46. up1(i,a[i]-old);
    47. up2(i,(i-1)*(a[i]-old));
    48. old=a[i];
    49. }
    50. while(m--){
    51. int l,r,q;
    52. cin>>l>>r>>q;
    53. up1(l,q);
    54. up2(l,(l-1)*q);
    55. up1(r+1,-q);
    56. up2(r+1,-q*r);
    57. }
    58. //查看[3,5]
    59. cout<<5*sum1(5)-sum2(5)-((3-1)*sum1(3-1)-sum2(3-1));
    60. return 0;
    61. }
    62. /*
    63. 7 3
    64. 1 2 3 4 5 6 7
    65. 1 4 2
    66. 3 6 6
    67. 1 3 1
    68. */

     

  • 相关阅读:
    【Java 基础篇】Java网络编程实时数据流处理
    记一次gateway微服务启动报错
    MySQL中的死锁
    SpringBoot整合JUnit
    猫吃什么罐头好?2023质量口碑好的猫罐头推荐!
    Quartus 使用 tcl 文件快速配置管脚
    欺诈团伙遇上关联网络,邪不压正
    [Linux] GRUB引导 学习笔记(一)
    手机号码格式校验:@Phone(自定义参数校验注解)
    S3E:用于协作SLAM的大规模多模态数据集
  • 原文地址:https://blog.csdn.net/gaoqiandr/article/details/127683702