zoukankan      html  css  js  c++  java
  • 操作格子

    一、题目分析

      刚刚看完题目的时候,我立马想到的是用数组来存储 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
    ",SegmentTreeGetWeightSum(segment_tree_node,operation[i][1],operation[i][2])); break; //求连续一段格子的权值和 
     49             case 3:printf("%d
    ",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 }
  • 相关阅读:
    jqGrid jqGrid 参数
    jqgrid问题总结
    quartz的配置表达式
    Struts2接收参数的几种方式
    Perl爬虫代码
    PHP官方的PECL扩展有问题
    Perl单URL爬虫
    Perl 多进程进度条
    Perl Tk摸索
    hdu 2058 数学题
  • 原文地址:https://www.cnblogs.com/LeoFeng/p/4346009.html
Copyright © 2011-2022 走看看