• 【数据结构】线段树


    知识点



    模板题

    1 . 基础版:洛谷 P3372 【模板】线段树 1 

    题目描述

    如题,已知一个数列,你需要进行下面两种操作:

    1. 将某区间每一个数加上 k。
    2. 求出某区间每一个数的和。

    输入格式

    第一行包含两个整数 n, m,分别表示该数列数字的个数和操作的总个数。

    第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

    接下来 m 行每行包含 3 或 4 个整数,表示一个操作,具体如下:

    1. 1 x y k:将区间 [x, y] 内每个数加上 k。
    2. 2 x y:输出区间 [x, y] 内每个数的和。

    输出格式

    输出包含若干行整数,即为所有操作 2 的结果。

    输入输出样例

    输入 #1

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

    输出 #1

    11
    8
    20

    说明/提示

    对于 30% 的数据:n8" role="presentation" style="position: relative;">n8m10" role="presentation" style="position: relative;">m10
    对于 70% 的数据:n103" role="presentation" style="position: relative;">n103m104" role="presentation" style="position: relative;">m104
    对于 100% 的数据:1n,m105" role="presentation" style="position: relative;">1n,m105

    保证任意时刻数列中任意元素的和在[263,263)" role="presentation" style="position: relative;">[263,263)内。

    【样例解释】


    直接暴力解决可以得到70分,后面的数据特别大,一个数一个数得增加会导致TLE:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. int n,m;
    7. const int maxn=1e7+5;
    8. int a[maxn];
    9. inline int read()
    10. {
    11. int x=0,f=1;
    12. char c=getchar();
    13. while(c<'0'||c>'9')
    14. {
    15. if(c=='-') f=-1;
    16. c=getchar();
    17. }
    18. while(c>='0' && c<='9')
    19. {
    20. x=(x<<3)+(x<<1)+(c^48);
    21. c=getchar();
    22. }
    23. return x*f;
    24. }
    25. inline int write(int x)
    26. {
    27. if(x>9) write(x/10);
    28. return putchar(x%10+'0');
    29. }
    30. int main()
    31. {
    32. n=read(); m=read();
    33. for(register int i=1;i<=n;++i)
    34. {
    35. a[i]=read();
    36. }
    37. for(register int i=1;i<=m;++i)
    38. {
    39. int num=read();
    40. if(num == 1)
    41. {
    42. int x=read(),y=read(),k=read();
    43. for(register int j=x;j<=y;++j)
    44. {
    45. a[j]+=k;
    46. }
    47. }
    48. else if(num == 2)
    49. {
    50. int x=read(),y=read(),sum=0;
    51. for(register int j=x;j<=y;++j)
    52. {
    53. sum+=a[j];
    54. }
    55. write(sum);
    56. putchar('\n');
    57. }
    58. }
    59. return 0;
    60. }

    通过剩下的数据需要采用线段树这个数据结构 :
     

    1. #include
    2. #include
    3. #include
    4. #include
    5. #define ll long long
    6. using namespace std;
    7. int n,m;
    8. const int maxn=1e6+5;
    9. int a[maxn];
    10. struct node
    11. {
    12. ll int left,right,sum,lazy;
    13. }tree[maxn<<2];
    14. inline int read()
    15. {
    16. int x=0,f=1;
    17. char c=getchar();
    18. while(c<'0'||c>'9')
    19. {
    20. if(c=='-') f=-1;
    21. c=getchar();
    22. }
    23. while(c>='0' && c<='9')
    24. {
    25. x=(x<<3)+(x<<1)+(c^48);
    26. c=getchar();
    27. }
    28. return x*f;
    29. }
    30. inline void build(int num,int l,int r)
    31. {
    32. tree[num].left=l; tree[num].right=r; //记录正在搜索的区间的左右节点
    33. if(l == r)
    34. //l==r时说明它就是最下层叶子节点,即只代表一个数,那么我们就将a数组的第l或r个元素映射上去。
    35. {
    36. tree[num].sum=a[l];
    37. return;
    38. }
    39. int mid=(l+r)>>1;
    40. build((num<<1),l,mid); //建立左子树
    41. build((num<<1|1),mid+1,r); //建立右子树
    42. tree[num].sum=tree[num<<1].sum+tree[num<<1|1].sum;
    43. //sum表示这段区间的区间和,所以这个节点的sum值等于它的儿子节点的值之和。
    44. }
    45. inline void put(int num)
    46. {
    47. //下传左儿子。
    48. tree[num<<1].lazy+=tree[num].lazy;
    49. tree[num<<1].sum+=(tree[num<<1].right-tree[num<<1].left+1)*tree[num].lazy;
    50. //下传右儿子。
    51. tree[num<<1|1].lazy+=tree[num].lazy;
    52. tree[num<<1|1].sum+=(tree[num<<1|1].right-tree[num<<1|1].left+1)*tree[num].lazy;
    53. tree[num].lazy=0; //将该点的标记清零
    54. }
    55. inline void update(int num,int l,int r,int k)
    56. {
    57. if((tree[num].rightr)) return;
    58. //如果需要访问的区间和现在所遍历到的这个区间完全不重合,那么返回,相当于剪枝。
    59. if((tree[num].right<=r)&&(tree[num].left>=l))
    60. {
    61. tree[num].lazy+=k;
    62. tree[num].sum+=(tree[num].right-tree[num].left+1)*k;
    63. return;
    64. }
    65. //如果需要访问的区间和当前遍历到的区间重合,那么就直接把lazy和sum更新后返回,无需再向下查找
    66. if(tree[num].lazy>0)
    67. {
    68. put(num); //之前遍历过,标记下传。
    69. }
    70. update((num<<1),l,r,k);
    71. update((num<<1|1),l,r,k); //递归更新左右儿子
    72. tree[num].sum=tree[num<<1].sum+tree[num<<1|1].sum;
    73. //递归回来后将左右子节点的sum加起来赋给父节点的sum。
    74. }
    75. inline ll int query(int num,int l,int r) //查找执行操作的结果,判断条件与合并的条件相同
    76. {
    77. if((tree[num].rightr)) return 0;
    78. if((tree[num].right<=r)&&(tree[num].left>=l)) return tree[num].sum;
    79. if(tree[num].lazy>0)
    80. {
    81. put(num);
    82. }
    83. return query((num<<1),l,r)+query((num<<1|1),l,r);
    84. }
    85. int main()
    86. {
    87. n=read(); m=read();
    88. for(int i=1;i<=n;++i)
    89. {
    90. a[i]=read();
    91. }
    92. build(1,1,n);
    93. for(int i=1;i<=m;++i)
    94. {
    95. int num=read();
    96. if(num == 1)
    97. {
    98. int x=read(),y=read(),k=read();
    99. update(1,x,y,k);
    100. }
    101. else if(num == 2)
    102. {
    103. int x=read(),y=read();
    104. ll int sum;
    105. sum=query(1,x,y);
    106. printf("%lld\n",sum);
    107. }
    108. }
    109. return 0;
    110. }

    2 . 提高版:洛谷 P3373 【模板】线段树 2

    题目描述

    如题,已知一个数列,你需要进行下面三种操作:

    • 将某区间每一个数乘上 x

    • 将某区间每一个数加上 x

    • 求出某区间每一个数的和

    输入格式

    第一行包含三个整数 n,m,p,分别表示该数列数字的个数、操作的总个数和模数。

    第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

    接下来 m 行每行包含若干个整数,表示一个操作,具体如下:

    操作 1: 格式:1 x y k  含义:将区间 [x,y] 内每个数乘上 k

    操作 2: 格式:2 x y k  含义:将区间 [x,y] 内每个数加上 k

    操作 3: 格式:3 x y  含义:输出区间 [x,y] 内每个数的和对 p 取模所得的结果

    输出格式

    输出包含若干行整数,即为所有操作 3 的结果。

    输入输出样例

    输入 #1

    5 5 38
    1 5 4 2 3
    2 1 4 1
    3 2 5
    1 2 4 2
    2 3 5 5
    3 1 4

    输出 #1

    17
    2

    说明/提示

    【数据范围】

    对于 30% 的数据:n8" role="presentation" style="position: relative;">n8m10" role="presentation" style="position: relative;">m10
    对于 70% 的数据:n103" role="presentation" style="position: relative;">n103m104" role="presentation" style="position: relative;">m104
    对于 100% 的数据:n105" role="presentation" style="position: relative;">n105m105" role="presentation" style="position: relative;">m105

    除样例外,p = 571373

    (数据已经过加强^_^)

    样例说明:

    故输出应为 17、2( 40mod38=2" role="presentation" style="position: relative;">40mod38=2 )


  • 相关阅读:
    docker入门加实战—docker数据卷
    速览muduo组成结构
    软件设计不是CRUD(9):低耦合模块设计理论——设计落地所面临的挑战
    微信小程序自定义按钮触发转发分享功能
    APP瀑布流分页问题——领取,使用等操作即消失问题
    C++——vector
    华为与美团达成合作,正式启动鸿蒙原生应用开发。
    解决 requests 2.28.x 版本 SSL 错误
    中微爱芯74逻辑兼容替代TI/ON/NXP工规品质型号全
    微信公众号订阅通知/一次性订阅通知
  • 原文地址:https://blog.csdn.net/gzkeylucky/article/details/126326239