• 【数据结构】—— 树状数组



    树状数组 

    一个正整数 x 的二进制表示为 a_{k-1}a_{k-2}...a_2a_1a_0 ,其中等于1的位是 \begin{Bmatrix} a_{i1},a_{i2},a_{i3}...a_{im} \end{Bmatrix}

    则 x 可以被二进制表示为 x=2^{i1}+2^{i2}+..+2^{im}

    不妨设 i1>i2>...im,进一步的,区间[1, x] 可以分成 O(logx) 个小区间

    这些小区间的共同特点是:若区间结尾为R,则区间长度就是等于R的“二进制分解”下最小的2的次幂,即 lowbit(R).

    例如:x=7=2^2+2^1+2^0,区间 [1, 7] 可以分成 [1, 4] [5, 6] [7, 7]

    长度分别是 lowbit(4) = 4, lowbit(6) = 2, lowbit(7) = 1 

    〔manim | 算法 | 数据结构〕 完全理解并深入应用树状数组 | 支持多种动态维护区间操作_哔哩哔哩_bilibili


    树状数组(Binary Indexed Tree)是一种 基于上述思想的数据结构,其基本用途就是维护序列的前缀和。对于给定的序列 a ,我们建立一个数组 c, 其中 c[x] 保存 a 的区间

    [x - lowbit(x) + 1, x] 中所有数的和, \large \sum_{i=x-lowbit(x)+1)}^{x} a[i] 


     

    黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有

    • 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[i - 2k+1] + A[i - 2k+2] + ... + A[i];   //k为i的二进制中从最低位到高位连续零的长度


    例如i = 8(1000)时候,k = 3,可自行验证。

    这个怎么实现求和呢,比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4];

    而根据上面的式子,容易得出 

    \large sumi = c[i] + c[i - 2^{k1}] + c[(i-2^{k1}) - 2^{k2}]+...

    其实树状数组就是一个二进制上面的应用。

    树状数组(BIT)—— 一篇就够了 - Last_Whisper - 博客园

    树状数组详解 - Xenny - 博客园


    AcWing 241. 楼兰图腾

    输入样例:

    1. 5
    2. 1 5 3 2 4

    输出样例:

    3 4

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 200010;
    7. typedef long long LL;
    8. int n;
    9. int a[N];
    10. int tr[N];
    11. int greaterr[N], lower[N];
    12. //返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
    13. int lowbit(int x)
    14. {
    15. return x & -x;
    16. }
    17. //将序列中第x个数加上k。
    18. void add(int x, int c)
    19. {
    20. for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
    21. }
    22. //查询序列前x个数的和
    23. int sum(int x)
    24. {
    25. int res = 0;
    26. for(int i = x; i; i -= lowbit(i)) res += tr[i];
    27. return res;
    28. }
    29. int main()
    30. {
    31. cin >> n;
    32. for(int i = 1; i <= n; i ++ ) scanf("%d",&a[i]);
    33. //从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
    34. for(int i = 1; i <= n; i ++ )
    35. {
    36. int y = a[i];
    37. //在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
    38. greaterr[i] = sum(n) - sum(y);
    39. //在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
    40. lower[i] = sum(y - 1);
    41. //将y加入树状数组,即数字y出现1次
    42. add(y,1);
    43. }
    44. //清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
    45. memset(tr, 0, sizeof tr);
    46. LL res1 = 0, res2 = 0;
    47. for(int i = n; i; i --)
    48. {
    49. int y = a[i];
    50. res1 += greaterr[i] * (LL)(sum(n) - sum(y));
    51. res2 += lower[i] * (LL)(sum(y - 1));
    52. //将y加入树状数组,即数字y出现1次
    53. add(y,1);
    54. }
    55. cout << res1 << " " << res2;
    56. return 0;
    57. }

    AcWing 242. 一个简单的整数问题

    输入样例:

    1. 10 5
    2. 1 2 3 4 5 6 7 8 9 10
    3. Q 4
    4. Q 1
    5. Q 2
    6. C 1 6 3
    7. Q 2

    输出样例:

    1. 4
    2. 1
    3. 2
    4. 5

    树状数组 + 差分 

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. typedef long long LL;
    7. const int N = 100010;
    8. int n, m;
    9. int a[N];
    10. LL tr[N];
    11. int lowbit(int x)
    12. {
    13. return x & -x;
    14. }
    15. void add(int x, int t)
    16. {
    17. for(int i = x; i <= n; i += lowbit(i)) tr[i] += t;
    18. }
    19. LL sum(int x)
    20. {
    21. LL res = 0;
    22. for(int i = x; i; i -= lowbit(i)) res += tr[i];
    23. return res;
    24. }
    25. int main()
    26. {
    27. cin >> n >> m;
    28. for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    29. for(int i = 1; i <= n; i ++ ) add(i, a[i] - a[i - 1]);
    30. while(m -- )
    31. {
    32. char op[2];
    33. int l, r, d;
    34. scanf("%s%d", op, &l);
    35. if(*op == 'C')
    36. {
    37. scanf("%d%d", &r, &d);
    38. add(l, d), add(r + 1, -d);
    39. }
    40. else
    41. {
    42. printf("%lld\n", sum(l));
    43. }
    44. }
    45. return 0;
    46. }

    AcWing 243. 一个简单的整数问题2 

    输入样例:

    1. 10 5
    2. 1 2 3 4 5 6 7 8 9 10
    3. Q 4 4
    4. Q 1 10
    5. Q 2 4
    6. C 3 6 3
    7. Q 2 4

    输出样例:

    1. 4
    2. 55
    3. 9
    4. 15

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. typedef long long LL;
    7. const int N = 100010;
    8. int n,m;
    9. int a[N];
    10. LL tr1[N]; //维护差分数组b[i]的前缀和
    11. LL tr2[N]; //维护b[i] * i 的前缀和
    12. int lowbit(int x)
    13. {
    14. return x & -x;
    15. }
    16. void add(LL tr[], int x, LL c)
    17. {
    18. for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
    19. }
    20. LL sum(LL tr[], int x)
    21. {
    22. LL res = 0;
    23. for(int i = x; i; i -= lowbit(i)) res += tr[i];
    24. return res;
    25. }
    26. LL prefix_sum(int x)
    27. {
    28. return sum(tr1, x) * (x + 1) - sum(tr2, x);
    29. }
    30. int main()
    31. {
    32. cin >> n >> m;
    33. for(int i = 1; i <= n; i ++ ) scanf("%d",&a[i]);
    34. for(int i = 1; i <= n; i ++ )
    35. {
    36. int b = a[i] - a[i - 1];
    37. add(tr1, i, b);
    38. add(tr2, i, (LL)i * b);
    39. }
    40. while(m --)
    41. {
    42. char op[2];
    43. int l,r,d;
    44. scanf("%s%d%d", op, &l, &r);
    45. if(*op == 'Q')
    46. {
    47. printf("%lld\n",prefix_sum(r) - prefix_sum(l - 1));
    48. }
    49. else
    50. {
    51. scanf("%d",&d);
    52. add(tr1, l, d), add(tr1, r + 1, -d);
    53. add(tr2, l, l * d), add(tr2, r + 1, (r + 1) * -d);
    54. }
    55. }
    56. return 0;
    57. }

     AcWing 244. 谜一样的牛 

    输入样例:

    1. 5
    2. 1
    3. 2
    4. 1
    5. 0

    输出样例:

    1. 2
    2. 4
    3. 5
    4. 3
    5. 1

     

    1. // 找到一个最小的x是sum(x) = k
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. const int N = 100010;
    7. int n;
    8. int h[N];
    9. int ans[N];
    10. int tr[N];
    11. int lowbit(int x)
    12. {
    13. return x & -x;
    14. }
    15. void add(int x, int c)
    16. {
    17. for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
    18. }
    19. int sum(int x)
    20. {
    21. int res = 0;
    22. for(int i = x; i; i -= lowbit(i)) res += tr[i];
    23. return res;
    24. }
    25. int main()
    26. {
    27. cin >> n;
    28. for(int i = 2; i <= n; i ++ ) scanf("%d", &h[i]);
    29. for(int i = 1; i <= n; i ++ ) tr[i] = lowbit(i);
    30. for(int i = n; i; i -- )
    31. {
    32. int k = h[i] + 1;
    33. int l = 1, r = n;
    34. while(l < r)
    35. {
    36. int mid = l + r >> 1;
    37. if(sum(mid) >= k) r = mid;
    38. else l = mid + 1;
    39. }
    40. ans[i] = r;
    41. add(r, -1);
    42. }
    43. for(int i = 1; i <= n; i ++) printf("%d\n", ans[i]);
    44. return 0;
    45. }
  • 相关阅读:
    tensorflow中的slim函数集合
    MySQL数据库查询对象空值判断与Java代码示例【含面试题】
    走向IPv6,阿里巴巴IPv6规模化部署实践
    ResNet50的猫狗分类训练及预测
    vue3中使用WangEditor 富文本编辑器
    硬盘压缩将C盘拓展成D盘和E盘
    手把手实例教你短视频定位,人设和变现方式,学会节省半年摸索时间
    httpdns是个什么技术,有什么用
    制作MySQL8绿色版&轻量版
    突破次元壁垒,让身边的玩偶手办在屏幕上动起来!
  • 原文地址:https://blog.csdn.net/forever_bryant/article/details/126116742