蓝桥杯 算法训练 操作格子

问题描述

有n个格子,从左到右放成一排,编号为1-n。

共有m次操作,有3种操作类型:
1.修改一个格子的权值,
2.求连续一段格子权值和,
3.求连续一段格子的最大值。

对于每个2、3操作输出你所求出的结果。

输入格式

第一行2个整数n,m。

接下来一行n个整数表示n个格子的初始权值。

接下来m行,每行3个整数p,x,y,p表示操作类型,

p=1时表示修改格子x的权值为y,

p=2时表示求区间[x,y]内格子权值和,

p=3时表示求区间[x,y]内格子最大的权值。

输出格式

有若干行,行数等于p=2或3的操作总数。

每行1个整数,对应了每个p=2或3操作的结果。

样例输入

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

样例输出

6
3

数据规模与约定

对于20%的数据n <= 100,m <= 200。
对于50%的数据n <= 5000,m <= 5000。
对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。

解题思路

由数据规模得,使用int数据类型即可。

注意,区间计数是从1开始的。

按照一般的思路提交,可是超时,刚好看到题目锦囊提示使用线段树。

线段树(Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。

简单的说,顾名思义,就是将一段线段(如:1-10),不断的划分,形成一棵树。

在这里,使用数组存放满二叉树,那数组的大小是多少呢?

在构建和利用线段树的过程中,充分使用了分治的思想。

题目中说,“p=1时表示修改格子x的权值为y”,这个x代表的是格子的位置。

创建的是1到n的线段树,其实每个结点的权重是不需要额外存储的,只需知道权重的和与最大值。叶子结点的sum和max即为权重。

注意,数组从1开始存储,利于二叉树结点位置的确定。

找了好几个小时的错误,原来是在找最大值的地方,把right写成了left,悲哀!忽然有种高中做数学题的悲哀。

代码实现

数组或容器

只能满足前50%的数据。

注意,在case语句中最好不要定义并初始化变量,常引起错误。

case后加大括号,可定义局部变量。

 
 
  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. int main(void) {
  6. int n, m;
  7. cin >> n >> m;
  8. vector<int> v;
  9. while(n--) {
  10. int tmp;
  11. cin >> tmp;
  12. v.push_back(tmp);
  13. }
  14. while(m--) {
  15. int p, x, y;
  16. cin >> p >> x >> y;
  17. int sum = 0;
  18. int max = 0;
  19. switch(p) {
  20. case 1 :
  21. v[x-1] = y;
  22. break;
  23. case 2 :
  24. for(int i = x; i <= y; i++) {
  25. sum += v[i - 1];
  26. }
  27. cout << sum << endl;
  28. break;
  29. case 3 :
  30. for(int i = x; i <= y; i++) {
  31. if(v[i - 1] > max) {
  32. max = v[i - 1];
  33. }
  34. }
  35. cout << max << endl;
  36. break;
  37. default :
  38. break;
  39. }
  40. }
  41. return 0;
  42. }

线段树

这里使用数组存放线段树,其实和线段树相关的操作只涉及到4种:

  1. 构建线段树
  2. 改变线段树中某个结点的值
  3. 求某区间的结点权重的和
  4. 求某区间的结点权重的最大值

只是存放线段树的数组的大小还没搞清楚,从coolnote下载的测试数据表明:
当n为100000时,最后一个不为0的数组索引为262142。

给定【1,n】,建立线性二叉树,其层数是确定的,根据二分原理,其层数为m=上取整(log(n))+1 , 那么数组上界为这棵满二叉树所有节点个数: 2^m-1=2^(上取整(log(n))+1)-1

举个例子,n=5,树层数为m=4,上界为2^4-1=15
n=6,树层数为m=4,上界为2^4-1=15
n=100000,树层数为m=18,上界为2^18-1=262143

 
 
  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4.  
  5. typedef struct SNode {
  6. int max, sum;
  7. int left, right;
  8. } SNode;
  9.  
  10. SNode t[300000];
  11.  
  12. int two_max(int a, int b) {
  13. return a > b ? a : b;
  14. }
  15.  
  16. void build(int pos, int l, int r) {
  17. t[pos].left = l;
  18. t[pos].right = r;
  19. t[pos].max = 0;
  20. t[pos].sum = 0;
  21. if(l == r) {
  22. return;
  23. }
  24. build(pos * 2, l, (l + r) / 2);
  25. build(pos * 2 + 1, (l + r) / 2 + 1, r);
  26. }
  27.  
  28. void change(int pos, int v, int w) {
  29. if(t[pos].left == v && t[pos].right == v) {
  30. t[pos].sum = w;
  31. t[pos].max = w;
  32. return;
  33. }
  34. int middle = (t[pos].left + t[pos].right) / 2;
  35. if(v <= middle) {
  36. change(pos * 2, v, w);
  37. } else {
  38. change(pos * 2 + 1, v, w);
  39. }
  40. t[pos].sum = t[pos * 2].sum + t[pos * 2 + 1].sum;
  41. t[pos].max = two_max(t[pos * 2].max, t[pos * 2 + 1].max);
  42. }
  43.  
  44. int get_sum(int pos, int l, int r) {
  45. if(t[pos].left == l && t[pos].right == r) {
  46. return t[pos].sum;
  47. }
  48. int middle = (t[pos].left + t[pos].right) / 2;
  49. if(r <= middle) {
  50. return get_sum(pos * 2, l, r);
  51. }
  52. if(l > middle) {
  53. return get_sum(pos * 2 + 1, l, r);
  54. }
  55. return get_sum(pos * 2, l, middle) + get_sum(pos * 2 + 1, middle + 1, r);
  56. }
  57.  
  58. int get_max(int pos, int l, int r) {
  59. if(t[pos].left == l && t[pos].right == r) {
  60. return t[pos].max;
  61. }
  62. int middle = (t[pos].left + t[pos].right) / 2;
  63. if(r <= middle) {
  64. return get_max(pos * 2, l, r);
  65. }
  66. if(l > middle) {
  67. return get_max(pos * 2 + 1, l, r);
  68. }
  69. return two_max(get_max(pos * 2, l, middle), get_max(pos * 2 + 1, middle + 1, r));
  70. }
  71.  
  72. int main(void) {
  73. memset(t, 0, sizeof(t));
  74. int n, m;
  75. cin >> n >> m;
  76. build(1, 1, n);
  77. for(int i = 1; i <= n; i++) {
  78. int tmp;
  79. cin >> tmp;
  80. change(1, i, tmp);
  81. }
  82. while(m--) {
  83. int p, x, y;
  84. cin >> p >> x >> y;
  85. switch(p) {
  86. case 1 :
  87. change(1, x, y);
  88. break;
  89. case 2 :
  90. cout << get_sum(1, x, y) << endl;
  91. break;
  92. case 3 :
  93. cout << get_max(1, x, y) << endl;
  94. break;
  95. }
  96. }
  97. return 0;
  98. }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值