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

算法训练 操作格子  
时间限制: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时表示求区间[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。

解题分析:

首先,我们初看到这个题目,你可能会用数组去解答,你可能这么想....

1:修改权值(直接将数组赋值);

2:求连续一段权值和(直接for循环搞定);

3:连续一段格子最大值:(还是for循环OK);

但是,我们再来仔细看一下题目:对于100%的数据1 <= n <= 100000,m <= 100000,0 <= 格子权值 <= 10000。时间限制:1.0s。

说明有10^5个格子,同时需要操作10^5次;操作1:时间复杂度O(1),但是操作2,3:时间复杂度O(n),所以执行完需要时间:

t = n * n = 10^10(10亿次)。而我们知道,计算机1s钟一般只能达到10^9(1亿次)。所以,如果我们用数组的话,会运行超时。

这里我们用另外一种方法,线段树,来解决这个问题。

线段树:

接下来我们分析一下何为线段树,简单的说,顾名思义,就是将一段线段(如:1-10),不断的划分,形成一棵树,如下图:


那我们把一段线段(1-10)搞成一棵树有什么用呢?(可能你会觉得好好的数组不用,搞这么深奥的树,最后叶子节点还不是1,2,3..,10么),不急,慢慢看。

目前这颗线段树还是一颗空树,因为每个结点上面我们还没有给它挂上一个值,接下来我们给结点挂值。

根据题目要求输入格子数目(假设n = 10,配合上面的图),然后给格子初始权值(如:1,2,3,4,5,6,7,8,9,10)

现在开始给线段树结点挂值:

  1. //定义结构体:线段树   
  2. typedef struct node  
  3. {  
  4.     int max, sum;         //统计线段树的最大值、和   
  5.     int left,right;           //线段树区间的左右值   
  6.     struct node *lchild;      //左子树   
  7.     struct node *rchild;      //右子树   
  8. }XNode;  

第一个值(权值:1):

第一步:1掉在(1-10)区间,该结点max = 1;  sum = 1;

第二步:1掉在(1-3)区间,该节点max = 1; sum = 1;

第三步:1掉在(1-2)区间,该结点max = 1, sum = 1;

第四步:1掉在(1)结点上,OVER

第二个值(权值:2):

第一步:2掉在(1-10)区间,因为2要大于开始的1,所以,max = 2;  sum = 1+2;

第二步:2掉在(1-3)区间,同上max = 2; sum = 1+2;

第三步:2掉在(1-2)区间,该结点max = 2, sum = 1+2;

第四步:2掉在(2)结点上,OVER

//.................省略一部分

第七个值(权值:7):

第一步:7掉在(1-10)区间,因为7要大于上一步的max,所以,max = 7;  sum = 1+2+3+4+5+6+7;

第二步:7掉在(6-10)区间,同上max = 7; sum = 6+7;

第三步:7掉在(6-8)区间,该结点max = 7, sum = 6+7;

第四步:7掉在(6-7)区间,该结点max = 7; sum = 6+7;

第五步:7掉在(7)结点上,OVER

一直到将最后一个值(10)挂上线段树上面。

此时线段树的状态:(按照结点从上往下写)

结点maxsum
(1,10)101+2+3+...+10
(1,5)51+2+3+4+5
(6,10)106+7+8+9+10
(1,3)31+2+3
(4,5)54+5
(6,8)86+7+8
(9,10)109+10
(1,2)21+2
333
444
555
(6,7)76+7
888
999
101010
111
222
666
777


根据上面这个表格,现在结果明确了吧?比如我们要得到(1-10)区间的最大值max,直接就可获得;需要(7-8)区间的和sum,也可直接获得。

这就是线段树的优点。

现在遇到另外一个问题:

现在遇到另外一个问题,就是格子权值修改问题,因为一旦我们修改了一个结点权值,它的父亲结点,父亲的父亲结点...它们的max和sum就需要改变。

你有没有很快的额想到递归?对,这里我们就是用递归解决的,首先一直递归找到需要修改的结点,将它的权值修改掉。

然后注意:在递归返回的时候,每返回一层,改变目前结点的max,sum,从而解决了问题。


比如:我们将4这个结点所挂的值修改为100(步骤如下)

第一步:递归,进入(1,10),进入(1,5),进入(4,5),进入(4),将4上面挂的值修改为100

ps:此时结点(4)max =100, sum = 100

第二步:第一次返回,进入(4,5),将max改为100,sum(4,5) = sum(4)+sum(5)

第三步:第二次返回,进入(1,5),将max改为100,sum(1,5) = sum(1,3) + sum(4,5)

第四步:第三次返回,进入(1,10),将max改为100,sum(1,10) = sum(1,5) + sum(6,10)........OVER

代码如下:

  1. //1.修改格子权值  
  2. void Modify(XNode *xTree, int point, int value)  
  3. {  
  4.     if (xTree->left == point && xTree->right == point) //找到该结点,修改   
  5.     {  
  6.         xTree->max = value;//修改最大值   
  7.         xTree->sum = value;//修改和   
  8.         return;  
  9.     }  
  10.     else  
  11.     {  
  12.         int mid = (xTree->left+xTree->right)/2;  
  13.         if (point <= mid)    //往左子树搜索   
  14.             Modify(xTree->lchild,point,value);   
  15.         else                 //往右子树搜索   
  16.             Modify(xTree->rchild,point,value);  
  17.         xTree->max = maxValue(xTree->lchild->max,xTree->rchild->max);//修改最大值:从下往上    
  18.         xTree->sum = xTree->lchild->sum + xTree->rchild->sum;        //修改和 :下->上   
  19.     }  
  20.     return;  
  21. }   

好了,问题就分析到这里了,另外如果还有不明白的地方直接看附录就行了,我认为注释已经够详细了。

附录:

  1. /* 
  2.     Name: 蓝桥杯:操作格子(线段树)  
  3.     Copyright: 供交流  
  4.     Author: Jopus  
  5.     Date: 05/02/14 23:06 
  6.     Description: dev-cpp 5.5.3  
  7. */  
  8.   
  9. #include <stdio.h>  
  10. #include <stdlib.h>  
  11. //定义结构体:线段树   
  12. typedef struct node  
  13. {  
  14.     int max, sum;         //统计线段树的最大值、和   
  15.     int left,right;       //线段树区间的左右值   
  16.     struct node *lchild;  //左子树   
  17.     struct node *rchild;  //右子树   
  18. }XNode;  
  19. //返回最大值  
  20. int maxValue(int max, int temp)  
  21. {  
  22.     if (temp > max)  
  23.         max = temp;  
  24.     return max;           //返回最大值   
  25. }   
  26. //创建线段树  
  27. XNode *CreateXTree(int left, int right) //传进区间左右值   
  28. {  
  29.     XNode *xTree = (XNode *)malloc(sizeof(XNode));  
  30.     xTree->left = left;    //给左端赋值   
  31.     xTree->right = right;  //给右端赋值  
  32.       
  33.     xTree->max = 0;        //线段树:结点维护内容  
  34.     xTree->sum = 0;   
  35.       
  36.     xTree->lchild = NULL;   //子树初始化 置空   
  37.     xTree->rchild = NULL;   //置空  
  38.     if (right != left)      //right != left  元区间   
  39.     {  
  40.         int mid = (left+right)/2;   //区间中点   
  41.         xTree->lchild = CreateXTree(left, mid);  //创建左子树  
  42.         xTree->rchild = CreateXTree(mid+1, right); //创建右子树   
  43.     }   
  44.     return xTree;  
  45. }   
  46. //插入一条线段  
  47. void Insert(XNode *xTree, int point, int value)  
  48. {  
  49.     xTree->sum += value;            //搜索树时,经过某区间 统计   
  50.     xTree->max = maxValue(xTree->max,value);//maxValue返回最大值   
  51.     if (xTree->left == xTree->right)//找到该线段   
  52.         return;  
  53.     else  
  54.     {  
  55.         if (point <= (xTree->left + xTree->right)/2)   
  56.             Insert(xTree->lchild,point,value);//左搜索   
  57.         else  
  58.             Insert(xTree->rchild,point,value);//右搜索   
  59.     }  
  60.     return;  
  61. }  
  62. //1.修改格子权值  
  63. void Modify(XNode *xTree, int point, int value)  
  64. {  
  65.     if (xTree->left == point && xTree->right == point) //找到该结点,修改   
  66.     {  
  67.         xTree->max = value;//修改最大值   
  68.         xTree->sum = value;//修改和   
  69.         return;  
  70.     }  
  71.     else  
  72.     {  
  73.         int mid = (xTree->left+xTree->right)/2;  
  74.         if (point <= mid)    //往左子树搜索   
  75.             Modify(xTree->lchild,point,value);   
  76.         else                 //往右子树搜索   
  77.             Modify(xTree->rchild,point,value);  
  78.         xTree->max = maxValue(xTree->lchild->max,xTree->rchild->max);//修改最大值:从下往上    
  79.         xTree->sum = xTree->lchild->sum + xTree->rchild->sum;        //修改和 :下->上   
  80.     }  
  81.     return;  
  82. }   
  83. //2.求连续一段格子权值和  
  84. int GeziSum(XNode *xTree, int left, int right)  
  85. {  
  86.     if (left == xTree->left && right == xTree->right) //找到该线段   
  87.         return xTree->sum;  
  88.     else  
  89.     {  
  90.         int mid = (xTree->left+xTree->right)/2;  
  91.         if (right <= mid)    //往左子树搜索   
  92.             return GeziSum(xTree->lchild,left,right);   
  93.         else if (left > mid) //往右子树搜索   
  94.             return GeziSum(xTree->rchild,left,right);  
  95.         else                 //分叉:左右搜索"和"值  
  96.             return GeziSum(xTree->lchild,left,mid) + GeziSum(xTree->rchild,mid+1,right);  
  97.     }  
  98. }   
  99. //3.求连续一段格子的最大值   
  100. int GeziMax(XNode *xTree, int left, int right)  
  101. {  
  102.     if (left == xTree->left && right == xTree->right) //找到该线段   
  103.         return xTree->max;  
  104.     else  
  105.     {  
  106.         int mid = (xTree->left+xTree->right)/2;  
  107.         if (right <= mid)    //往左子树搜索   
  108.             return GeziMax(xTree->lchild,left,right);   
  109.         else if (left > mid) //往右子树搜索   
  110.             return GeziMax(xTree->rchild,left,right);  
  111.         else  //分叉:返回搜索到的最大值   
  112.             return maxValue(GeziMax(xTree->lchild,left,mid),GeziMax(xTree->rchild,mid+1,right));  
  113.     }  
  114. }   
  115. //主函数   
  116. int main()  
  117. {  
  118.     int m = 0, n = 0, i = 0, j = 0;  
  119.     XNode *xTree = NULL;  
  120.     int input[100000][3] = {0};  //input[][0]:操作序号,input[][1]:x,input[][2]:y   
  121.     int Gezi = 0;   
  122.     scanf("%d%d",&n,&m);         //n:格子个数, m:操作次数  
  123.     xTree = CreateXTree(1,n);    //创建线段树,区间:1~n   
  124.     for (i = 1; i <= n; ++i)     //给格子赋权值,Gezi;  
  125.     {   
  126.         scanf("%d",&Gezi);   
  127.         Insert(xTree,i,Gezi);    //给线段树赋值   
  128.     }  
  129.     for (i = 0; i < m; ++i)  
  130.         for (j = 0; j < 3; ++j)  //一个循环,输入3次:0,1,2   
  131.             scanf("%d",&input[i][j]);  
  132.     for (i = 0; i < m; ++i)      //执行操作   
  133.     {  
  134.         switch(input[i][0])  
  135.         {  
  136.             case 1:Modify(xTree,input[i][1],input[i][2]); break;//修改线段权值   
  137.             case 2:printf("%d\n",GeziSum(xTree,input[i][1],input[i][2])); break;//返回线段和   
  138.             case 3:printf("%d\n",GeziMax(xTree,input[i][1],input[i][2])); break;//返回线段最大值   
  139.             default:break;  
  140.         }  
  141.     }  
  142.     return 0;  
  143. }   


参考文献:

程序猿之洞CSDN博客 http://blog.csdn.net/acmman/article/details/18631017 ,2014年2月9日
百度百科,http://baike.baidu.com/view/670683.htm ,2014年2月9日
提交序号 姓名 试题名称 提交时间  
代码长度
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  • CPU使用  
    内存使用  
    评测详情
    62664 Jopus 操作格子 02-07 16:28 4.262KB C 正确 100 234ms 8.019MB 评测详情

    转载请保留原文地址:http://blog.csdn.net/jpous/article/details/18965325

    • 1
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值