线段树
来源:互联网 发布:flord算法 编辑:程序博客网 时间:2024/04/29 22:57
有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操作的结果。
1 2 3 4
2 1 3
1 4 3
3 1 4
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)
现在开始给线段树结点挂值:
//定义结构体:线段树 typedef struct node { int max, sum; //统计线段树的最大值、和 int left,right; //线段树区间的左右值 struct node *lchild; //左子树 struct node *rchild; //右子树 }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)挂上线段树上面。
此时线段树的状态:(按照结点从上往下写)
根据上面这个表格,现在结果明确了吧?比如我们要得到(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.修改格子权值 void Modify(XNode *xTree, int point, int value) { if (xTree->left == point && xTree->right == point) //找到该结点,修改 { xTree->max = value;//修改最大值 xTree->sum = value;//修改和 return; } else { int mid = (xTree->left+xTree->right)/2; if (point <= mid) //往左子树搜索 Modify(xTree->lchild,point,value); else //往右子树搜索 Modify(xTree->rchild,point,value); xTree->max = maxValue(xTree->lchild->max,xTree->rchild->max);//修改最大值:从下往上 xTree->sum = xTree->lchild->sum + xTree->rchild->sum; //修改和 :下->上 } return; }好了,问题就分析到这里了,另外如果还有不明白的地方直接看附录就行了,我认为注释已经够详细了。
附录:
/* Name: 蓝桥杯:操作格子(线段树) Copyright: 供交流 Author: Jopus Date: 05/02/14 23:06 Description: dev-cpp 5.5.3 */ #include <stdio.h> #include <stdlib.h> //定义结构体:线段树 typedef struct node { int max, sum; //统计线段树的最大值、和 int left,right; //线段树区间的左右值 struct node *lchild; //左子树 struct node *rchild; //右子树 }XNode; //返回最大值 int maxValue(int max, int temp) { if (temp > max) max = temp; return max; //返回最大值 } //创建线段树 XNode *CreateXTree(int left, int right) //传进区间左右值 { XNode *xTree = (XNode *)malloc(sizeof(XNode)); xTree->left = left; //给左端赋值 xTree->right = right; //给右端赋值 xTree->max = 0; //线段树:结点维护内容 xTree->sum = 0; xTree->lchild = NULL; //子树初始化 置空 xTree->rchild = NULL; //置空 if (right != left) //right != left 元区间 { int mid = (left+right)/2; //区间中点 xTree->lchild = CreateXTree(left, mid); //创建左子树 xTree->rchild = CreateXTree(mid+1, right); //创建右子树 } return xTree; } //插入一条线段 void Insert(XNode *xTree, int point, int value) { xTree->sum += value; //搜索树时,经过某区间 统计 xTree->max = maxValue(xTree->max,value);//maxValue返回最大值 if (xTree->left == xTree->right)//找到该线段 return; else { if (point <= (xTree->left + xTree->right)/2) Insert(xTree->lchild,point,value);//左搜索 else Insert(xTree->rchild,point,value);//右搜索 } return; } //1.修改格子权值 void Modify(XNode *xTree, int point, int value) { if (xTree->left == point && xTree->right == point) //找到该结点,修改 { xTree->max = value;//修改最大值 xTree->sum = value;//修改和 return; } else { int mid = (xTree->left+xTree->right)/2; if (point <= mid) //往左子树搜索 Modify(xTree->lchild,point,value); else //往右子树搜索 Modify(xTree->rchild,point,value); xTree->max = maxValue(xTree->lchild->max,xTree->rchild->max);//修改最大值:从下往上 xTree->sum = xTree->lchild->sum + xTree->rchild->sum; //修改和 :下->上 } return; } //2.求连续一段格子权值和 int GeziSum(XNode *xTree, int left, int right) { if (left == xTree->left && right == xTree->right) //找到该线段 return xTree->sum; else { int mid = (xTree->left+xTree->right)/2; if (right <= mid) //往左子树搜索 return GeziSum(xTree->lchild,left,right); else if (left > mid) //往右子树搜索 return GeziSum(xTree->rchild,left,right); else //分叉:左右搜索"和"值 return GeziSum(xTree->lchild,left,mid) + GeziSum(xTree->rchild,mid+1,right); } } //3.求连续一段格子的最大值 int GeziMax(XNode *xTree, int left, int right) { if (left == xTree->left && right == xTree->right) //找到该线段 return xTree->max; else { int mid = (xTree->left+xTree->right)/2; if (right <= mid) //往左子树搜索 return GeziMax(xTree->lchild,left,right); else if (left > mid) //往右子树搜索 return GeziMax(xTree->rchild,left,right); else //分叉:返回搜索到的最大值 return maxValue(GeziMax(xTree->lchild,left,mid),GeziMax(xTree->rchild,mid+1,right)); } } //主函数 int main() { int m = 0, n = 0, i = 0, j = 0; XNode *xTree = NULL; int input[100000][3] = {0}; //input[][0]:操作序号,input[][1]:x,input[][2]:y int Gezi = 0; scanf("%d%d",&n,&m); //n:格子个数, m:操作次数 xTree = CreateXTree(1,n); //创建线段树,区间:1~n for (i = 1; i <= n; ++i) //给格子赋权值,Gezi; { scanf("%d",&Gezi); Insert(xTree,i,Gezi); //给线段树赋值 } for (i = 0; i < m; ++i) for (j = 0; j < 3; ++j) //一个循环,输入3次:0,1,2 scanf("%d",&input[i][j]); for (i = 0; i < m; ++i) //执行操作 { switch(input[i][0]) { case 1:Modify(xTree,input[i][1],input[i][2]); break;//修改线段权值 case 2:printf("%d\n",GeziSum(xTree,input[i][1],input[i][2])); break;//返回线段和 case 3:printf("%d\n",GeziMax(xTree,input[i][1],input[i][2])); break;//返回线段最大值 default:break; } } return 0; }
参考文献:
程序猿之洞CSDN博客 http://blog.csdn.net/acmman/article/details/18631017 ,2014年2月9日
百度百科,http://baike.baidu.com/view/670683.htm ,2014年2月9日
转载请保留原文地址:http://blog.csdn.net/jpous/article/details/18965325- 线段树?线段树!
- 线段树?线段树!
- 线段_线段树
- 线段_线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 写一个简单的按钮集合
- 带有引导小点的ViewPager的实现
- Java排序算法
- 手把手教你做手机婚恋网
- android studio versionName一直都是1.0的解决办法
- 线段树
- hdu -1069 Monkey and Banana【贪心+dp】
- QtCreator-----创建GUI项目
- win8安装python2.7.msi出错的解决方法
- BestCoder Round #74 (div.2) LCP Array
- 如何只用CSS做到完全居中
- ubuntu中各个版本java的安装
- 决战Offer---计算机网络
- Android Studio将Eclipse的项目作为module的依赖库