操作格子

一、题目分析

  刚刚看完题目的时候,我立马想到的是用数组来存储 n 个格子的权值,但是,这样做的话,没有办法在时间限制的 1s 完成操作,出现运行超时。

  这是因为题目的数据规模与约定是:对于 100% 的数据 1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。详细分析如下:

  由题目可知:操作 1、2 和 3 的时间复杂度分别为:O(1)、O(n) 和 O(n),当 n=100000=10^5,m=100000=10^5 时,所需要运算的次数为0^5*10^5=10^10(次)。再看看我们手上的和市场上的个人电脑,CPU 的主频一般只有 GHz 的数量级,也就是 1s 一般只能运算 10^9(次),无法再题目的时间限制 (1s) 之内完成 10^10(次) 运算,所以会出现运行超时。

  因此,这里我们需要选择线段树这种数据结构代替数组来解决这个问题。

二、线段树介绍

  线段树将一个线段(区间,如:[1,10])不断地划分,直到划分成一些单元区间,最后形成一棵树,每个单元区间对应线段树中的一个子叶结点。如下图所示:

  对于线段树中的每一个非叶子节点 [a,b],它的左儿子表示的区间为 [a,(a+b)/2],右儿子表示的区间为 [(a+b)/2+1,b],最后的子节点数目为 n,即整个线段区间的长度。

三、算法设计

  上图所示的线段树是一棵空树,因为每个结点上面还没有挂值,下面给这颗树挂值,并详细叙述线段树在本题的用处。

  由题目可知,要求输入格子数目,然后给格子赋初始权值。这里假设格子的数目 n=10,初始权值分别为 1,2,3,4,5,6,7,8,9,10。

  首先,定义线段树的节点结构体如下:

1 //定义结构体:线段树的节点
2 typedef struct SegmentTreeNode
3 {
4     int left,right; //分别用于记录线段区间的左端点和右端点的值
5     int weight_sum,weight_max; //分别用于记录连续一段格子的权值和与连续一段格子的最大值
6     struct SegmentTreeNode *left_child,*right_child; //分别用于指向该线段树节点的左和右子节点
7 }STNode;

  然后,逐一给这棵树挂值:

  1. 权值 1

  • 1 在区间 [1,10] 里,该结点 weight_sum=1,weight_max=1;
  • 1 在区间 [1,5] 里,该节点 weight_sum=1,weight_max=1;
  • 1 在区间 [1,3] 里,该节点 weight_sum=1,weight_max=1;
  • 1 在区间 [1,2] 里,该结点 weight_sum=1,weight_max=1;
  • 1 在区间 [1,1] 里,该结点 weight_sum=1,weight_max=1。

  2. 权值 2

  • 2 在区间 [1,10] 里,因为 2 大于上面的 weight_max=1,所以 weight_sum=1+2=3,weight_max=2;
  • 2 在区间 [1,5] 里,同上, weight_sum=1+2=3,weight_max=2;
  • 2 在区间 [1,3] 里,同上, weight_sum=1+2=3,weight_max=2;
  • 2 在区间 [1,2] 里,同上, weight_sum=1+2=3,weight_max=2;
  • 2 在区间 [2,2] 里,同上, weight_sum=1+2=3,weight_max=2。

  以此类推,直到最后一个权值 10 挂在线段树上。最后得到的线段树各个线段(区间)节点的状态如下:

线段(区间) weight_max weight_sum
[1,10] 10 1+2+3+...+10
[1,5] 5 1+2+3+4+5
[6,10] 10 6+7+8+9+10
[1,3] 3 1+2+3
[4,5] 5 4+5
[6,8] 8 6+7+8
[9,10] 10 9+10
[1,2] 2 1+2
3 3 3
4 4 4
5 5 5
[6,7] 7 6+7
8 8 8
9 9 9
10 10 10
1 1 1
2 2 2
6 6 6
7 7 7

  根据上面的表格,回看一下题目的要求(操作类型):

  1. 修改一个格子的权值
  2. 求连续一段格子权值和
  3. 求连续一段格子的最大值

  显然,对于操作类型 2 和 3,我们可以直接从线段(区间)节点中获得,这就是在本题中使用线段树的好处。

  对于操作类型 1,当我们找到要修改权值的格子后,显然不能只对该格子的权值作简单的修改,因为修改了该格子的权值后,它的前驱节点的权值和 (weight_max) 和权值最大值 (weight_sum) 也需要作相应的改变。但是,这个问题我们很容易可以想到使用递归的方法来解决,使函数调用在递归返回的时候,每向上返回一层,对权值和 (weight_max) 和权值最大值 (weight_sum) 也作相应的修改。

四、程序设计:

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3
  4 //定义结构体:线段树的节点
  5 typedef struct SegmentTreeNode
  6 {
  7     int left,right; //分别用于记录线段区间的左端点和右端点的值
  8     int weight_sum,weight_max; //分别用于记录连续一段格子的权值和与连续一段格子的最大值
  9     struct SegmentTreeNode *left_child,*right_child; //分别用于指向该线段树节点的左和右子节点
 10 }STNode;
 11
 12 //函数声明
 13 STNode *SegmentTreeInit(int left, int right); //线段树初始化
 14 int GetMaximum(int a,int b); //得到最大值
 15 void SegmentTreeAssignment(STNode *segment_tree_node,int i,int weight); //给线段树中的指定的单元区间节点赋权值
 16 void SegmentTreeModification(STNode *segment_tree_node,int i,int weight); //修改指定格子(单元区间节点)的权值
 17 int SegmentTreeGetWeightSum(STNode *segment_tree_node,int left,int right); //求连续一段格子的权值和
 18 int SegmentTreeGetWeightMaximum(STNode *segment_tree_node,int left,int right); //求连续一段格子的最大值
 19
 20 //主函数
 21 int main()
 22 {
 23     int i,j;
 24     int n,m; //分别用于记录输入的格子的数目和操作的次数
 25     STNode *segment_tree_node; //用于指向根据输入的格子数 n 初始化的线段树 [1,n]
 26     int weight; //用于记录输入的格子的权值
 27     int operation[100000][3]={0}; //用于记录操作类型和参数:operation[][0]:操作序号,operation[][1]:x,operation[][2]:y
 28
 29     scanf("%d%d",&n,&m); //输入格子的数目 n 和操作的次数 m
 30
 31     segment_tree_node=SegmentTreeInit(1,n); //线段树初始化
 32
 33     //初始化权值
 34     for(i=1;i<=n;i++)
 35     {
 36         scanf("%d",&weight); //输入权值
 37         SegmentTreeAssignment(segment_tree_node,i,weight); //给线段树中的指定的单元区间节点赋权值
 38     }
 39
 40     //输入 m 次操作并执行
 41     for(i=0;i<m;i++)
 42         for(j=0;j<3;j++)
 43             scanf("%d",&operation[i][j]); //输入操作类型和参数
 44     for(i=0;i<m;i++) //执行操作
 45         switch(operation[i][0])
 46         {
 47             case 1:SegmentTreeModification(segment_tree_node,operation[i][1],operation[i][2]); break; //修改指定格子(单元区间节点)的权值
 48             case 2:printf("%d\n",SegmentTreeGetWeightSum(segment_tree_node,operation[i][1],operation[i][2])); break; //求连续一段格子的权值和
 49             case 3:printf("%d\n",SegmentTreeGetWeightMaximum(segment_tree_node,operation[i][1],operation[i][2])); break; //求连续一段格子的最大值
 50             default:break;
 51         }
 52
 53     return 0;
 54 }
 55
 56 /*********************************************************************************************************
 57 ** 函数功能 :线段树初始化
 58 ** 函数说明 :无
 59 ** 入口参数 :left:线段树当前节点的区间左端点值
 60 **            :right:线段树当前节点的区间右端点值
 61 ** 出口参数 :指向该线段树的指针(初始化后的线段树的第一个节点的地址)
 62 *********************************************************************************************************/
 63 STNode *SegmentTreeInit(int left,int right)
 64 {
 65     STNode *segment_tree_node=(STNode *)malloc(sizeof(STNode)); //申请 sizeof(STNode) 大小的内存空间,新建一个线段树节点
 66
 67     segment_tree_node->left=left; //新建节点的区间左端点赋初值 left
 68     segment_tree_node->right=right; //新建节点的区间右端点赋初值 right
 69     segment_tree_node->weight_sum=0; //新建节点中记录的连续一段格子的权值和赋初值 0
 70     segment_tree_node->weight_max=0; //新建节点中记录的连续一段格子的权值最大值赋初值 0
 71     segment_tree_node->left_child=NULL; //新建节点中指向其左子节点的指针赋初值 NULL
 72     segment_tree_node->right_child=NULL; //新建节点中指向其右子节点的指针赋初值 NULL
 73
 74     if(right!=left) //如果新建的节点的区间不是单元区间,则线段树未完成初始化,继续分割区间
 75     {
 76         int middle=(left+right)/2; //获得新建节点区间的中值
 77         segment_tree_node->left_child=SegmentTreeInit(left,middle); //继续初始化新建节点的左子树
 78         segment_tree_node->right_child=SegmentTreeInit(middle+1,right); //继续初始化新建节点的右子树
 79     }
 80
 81     return segment_tree_node; //返回指向该线段树的指针(初始化后的线段树的第一个节点的地址)
 82 }
 83
 84 /*********************************************************************************************************
 85 ** 函数功能 :得到最大值
 86 ** 函数说明 :无
 87 ** 入口参数 :a、b:进行比较的数
 88 ** 出口参数 :比较后的最大值
 89 *********************************************************************************************************/
 90 int GetMaximum(int a,int b)
 91 {
 92     if(a>b)
 93         return a;
 94     else
 95         return b;
 96 }
 97
 98 /*********************************************************************************************************
 99 ** 函数功能 :给线段树中的指定的单元区间节点赋权值
100 ** 函数说明 :在给指定单元区间节点赋权值的同时,更新包含该单元区间节点的区间节点的权值和与最大值
101 ** 入口参数 :segment_tree_node:指向线段树的指针
102             :i:要赋权值线段树的单元区间节点 i
103 **            :weight:要赋的权值
104 ** 出口参数 :无
105 *********************************************************************************************************/
106 void SegmentTreeAssignment(STNode *segment_tree_node,int i,int weight)
107 {
108     segment_tree_node->weight_sum+=weight; //更新包含单元区间节点 i 的区间节点中的权值和
109     segment_tree_node->weight_max=GetMaximum(segment_tree_node->weight_max,weight); //更新包含单元区间节点 i 的区间节点中的权值最大值
110
111     //寻找单元区间节点 i
112     if(segment_tree_node->left==segment_tree_node->right) //搜索到单元区间节点 i
113         return;
114     else //没有,继续搜索
115         if (i<=(segment_tree_node->left+segment_tree_node->right)/2)
116             SegmentTreeAssignment(segment_tree_node->left_child,i,weight); //往左子树搜索
117         else
118             SegmentTreeAssignment(segment_tree_node->right_child,i,weight); //往右子树搜索
119
120     return;
121 }
122
123 /*********************************************************************************************************
124 ** 函数功能 :修改指定格子(单元区间节点)的权值
125 ** 函数说明 :在修改指定格子(单元区间节点)权值的同时,修改包含该单元区间节点的区间节点的权值和与最大值
126 ** 入口参数 :segment_tree_node:指向线段树的指针
127             :i:要修改权值的格子(单元区间节点) i
128 **            :weight:要修改的权值
129 ** 出口参数 :无
130 *********************************************************************************************************/
131 void SegmentTreeModification(STNode *segment_tree_node,int i,int weight)
132 {
133     if(segment_tree_node->left==i&&segment_tree_node->right==i) //搜索到该格子(单元区间节点)
134     {
135         segment_tree_node->weight_sum=weight; //修改指定格子(单元区间节点)中记录的连续一段格子的权值和
136         segment_tree_node->weight_max=weight; //修改指定格子(单元区间节点)中记录的连续一段格子的权值最大值
137         return;
138     }
139     else //没有,继续搜索
140     {
141         int middle=(segment_tree_node->left+segment_tree_node->right)/2; //获得当前节点区间的中值
142         if(i<=middle) //往左子树搜索
143             SegmentTreeModification(segment_tree_node->left_child,i,weight);
144         else //往右子树搜索
145             SegmentTreeModification(segment_tree_node->right_child,i,weight);
146
147         segment_tree_node->weight_sum=segment_tree_node->left_child->weight_sum+segment_tree_node->right_child->weight_sum; //修改包含单元区间节点 i 的区间节点中的权值和
148         segment_tree_node->weight_max=GetMaximum(segment_tree_node->left_child->weight_max,segment_tree_node->right_child->weight_max); //修改包含单元区间节点 i 的区间节点中的权值最大值
149     }
150
151     return;
152 }
153
154 /*********************************************************************************************************
155 ** 函数功能 :求连续一段格子的权值和
156 ** 函数说明 :无
157 ** 入口参数 :segment_tree_node:指向线段树的指针
158             :left:连续一段格子的左端点值
159 **            :right:连续一段格子的右端点值
160 ** 出口参数 :连续一段格子的权值和
161 *********************************************************************************************************/
162 int SegmentTreeGetWeightSum(STNode *segment_tree_node,int left,int right)
163 {
164     if(segment_tree_node->left==left&&segment_tree_node->right==right) //搜索到该段格子
165         return segment_tree_node->weight_sum;
166     else
167     {
168         int middle=(segment_tree_node->left+segment_tree_node->right)/2;
169         if (right<=middle) //往左子树搜索
170             return SegmentTreeGetWeightSum(segment_tree_node->left_child,left,right);
171         else if(left>middle) //往右子树搜索
172             return SegmentTreeGetWeightSum(segment_tree_node->right_child,left,right);
173         else //分别往左子树和右子树搜索,最后相加
174             return SegmentTreeGetWeightSum(segment_tree_node->left_child,left,middle)+SegmentTreeGetWeightSum(segment_tree_node->right_child,middle+1,right);
175     }
176 }
177
178 /*********************************************************************************************************
179 ** 函数功能 :求连续一段格子的最大值
180 ** 函数说明 :无
181 ** 入口参数 :segment_tree_node:指向线段树的指针
182             :left:连续一段格子的左端点值
183 **            :right:连续一段格子的右端点值
184 ** 出口参数 :连续一段格子的最大值
185 *********************************************************************************************************/
186 int SegmentTreeGetWeightMaximum(STNode *segment_tree_node,int left,int right)
187 {
188     if(segment_tree_node->left==left&&segment_tree_node->right==right) //搜索到该段格子
189         return segment_tree_node->weight_max;
190     else
191     {
192         int middle=(segment_tree_node->left+segment_tree_node->right)/2;
193         if(right<=middle) //往左子树搜索
194             return SegmentTreeGetWeightMaximum(segment_tree_node->left_child,left,right);
195         else if (left>middle) //往右子树搜索
196             return SegmentTreeGetWeightMaximum(segment_tree_node->right_child,left,right);
197         else  //分叉:返回搜索到的最大值
198             return GetMaximum(SegmentTreeGetWeightMaximum(segment_tree_node->left_child,left,middle),SegmentTreeGetWeightMaximum(segment_tree_node->right_child,middle+1,right));
199     }
200 }
时间: 2024-08-17 04:17:17

操作格子的相关文章

java 操作格子问题(线段树)

很久之前做过线段树的问题(操作格子),时间长了之后再次接触到,发现当初理解的不是很透彻,然后代码冗长,再遇到的时候发现自己甚至不能独立地完成这个问题. 所以算法这个东西啊, 第一,是要经常练习(我个人认为-每一个程序员都不应该不擅长算法-从今天开始,要常写博客!). 第二,是一定要理解透彻,理解透彻并不是说到网上找到了解答,然后自己照着能够运行出来,这样是不够的!甚至不是说你看完了一个算法之后,完全不看他的解答,然后你自己写出来,这样也是不够的! 先贴题目: 问题描述 有n个格子,从左到右放成一

蓝桥杯 - 操作格子 (线段树)

题目传送:操作格子 思路:简单线段树,单点更新,区间求和以及最值 AC代码: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cmath> #include <queue> #include <stack> #include <vector> #include <map>

【蓝桥杯】操作格子

问题描述 有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

算法训练 操作格子

算法训练 操作格子 时间限制:1.0s   内存限制:256.0MB 问题描述 有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时表示

蓝桥杯《操作格子》

解题过程:题意通俗易懂,看完题目以为是普通的对数组进行操作的题目,但是看到输入的数据范围后,发现并不是.但是因为没有好的想法,只能用最简单的方式做,毫无意外的超时了.然后找到这篇博文:http://blog.csdn.net/qq_33245342/article/details/54892576 问题描述  有n个格子,从左到右放成一排,编号为1-n. 共有m次操作,有3种操作类型: 1.修改一个格子的权值, 2.求连续一段格子权值和, 3.求连续一段格子的最大值. 对于每个2.3操作输出你所

蓝桥杯 算法训练 操作格子 [ 线段树 ]

传送门 算法训练 操作格子 时间限制:1.0s   内存限制:256.0MB 锦囊1 锦囊2 锦囊3 问题描述 有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

算法训练 操作格子 线段树板子题

问题描述 有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

操作格子(蓝桥杯)

问题描述 有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

蓝桥杯-算法训练--ALGO-8 操作格子

问题描述 有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或