操作格子

 

一、题目分析

  刚刚看完题目的时候,我立马想到的是用数组来存储 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_maxweight_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 }
posted @ 2015-03-17 23:30  LeoFeng  阅读(673)  评论(0编辑  收藏  举报