题目链接:
http://lx.lanqiao.cn/problem.page?gpid=T18
题目描述:
有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。
解题思路:
乍一看,不怎么难。然而写一波循环超时之后。
本题求和、最大值操作显然不能循环计算求取,那么就要想办法再此之前保存这些值。那么问题就来了,我们要怎样保存呢?
先介绍一下线段树:
线段树是一种二叉搜索树,与区间树相似,线段树是建立在线段的基础上,每个结点都代表了一条线段[a,b]。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N(这个我表示呵呵)
对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
线段树至少支持下列操作:
Insert(t,x):将包含在区间 int 的元素 x 插入到树t中;
Delete(t,x):从线段树 t 中删除元素 x;
Search(t,x):返回一个指向树 t 中元素 x 的指针。
好,我们把其代入本题:
首先说一下建树:void build(int i,int l,int r)//引入节点标和左右标
假设输入n=8,那我们需要构造的线段即[1,8],即我们的建树操作为:build(1,1,8); //首先从节点1开始,左极限为1,右极限为8
我们的build函数有以下几个操作:
①如果l==r,也就是说,当前引入点左右相等,那么就是一个单独的数,我们就不再需要向下递归
②如果l不等于r,我们需要创造他的2个子节点:
build(2*i,l,(l+r)/2); //我们知道二叉树子节点的编号分别为2i,2i+1
build(2*i+1,(l+r)/2+1,r); //我们递归构造子节点时,对于奇数线段比如说[1-3],我们有两种选择,[1-2][3]或者[1-2][2-3] 这里我们采取前者。
③递归回归时将其有用的权值返回父节点,父节点针对于子节点的权值构造自身权值
比如这道题的sum与max:
/* 回归时得到当前i节点的max信息 */
LineTree[i].Node_Max=fmax(LineTree[2*i].Node_Max,LineTree[2*i+1].Node_Max);
/* 回归时得到当前i节点的sum信息 */
LineTree[i].Node_Sum=LineTree[2*i].Node_Sum+LineTree[2*i+1].Node_Sum;
接下来我们讲更新:
①当叶子节点更新的时候,所有其父路径直至根节点走要进行一次更新操作,即时间复杂度为O(logN)
②找叶子节点也需要花费O(logN)从根节点递归查找至叶子节点
最后,我们讲查询:
①查询分为点查询,线段查询(即线段左右点是否相等)
②线段查询,该线段可能在树中完全包含,也可能是半包含。例如构造线段[1,2,3,4]
即:
[1,2,3,4]
[1,2] [3,4]
[1] [2] [3] [4]
如果我们想搜寻[1,3]线段,那么可以看到,该树是不存在这样的线段的,那么我们就需要通过[1,2]和[3]来计算[1,2,3]所返回的权值
下面贴出完整代码:
#include<stdio.h>#define N 100010 struct LineTree { int Node_Max;//i点线段的最大值 int Node_Sum;//i点线段的和 int Node_Lpoint;//i点线段的左限 int Node_Rpoint;//i点线段的右限 }LineTree[N*4]; //不是2n-1!!!不是2n-1!!!不是2n-1!!! int Box[N]; // 盒子 int _max;int _sum;int fmax(int a,int b){ return a>b?a:b;}void build(int i,int l,int r)//引入节点标和左右标 { LineTree[i].Node_Lpoint=l, LineTree[i].Node_Rpoint=r; if(l==r) //如果左右相等,表示只有一个元素 LineTree[i].Node_Sum=LineTree[i].Node_Max=Box[l]; //节点记录该单元素 else {/* 递归构造左右子树 */ build(2*i,l,(l+r)/2);build(2*i+1,(l+r)/2+1,r);/* 回归时得到当前i节点的max信息 */ LineTree[i].Node_Max=fmax(LineTree[2*i].Node_Max,LineTree[2*i+1].Node_Max);/* 回归时得到当前i节点的sum信息 */ LineTree[i].Node_Sum=LineTree[2*i].Node_Sum+LineTree[2*i+1].Node_Sum;}}void updata(int i,int x,int y)/*单节点更新 y替换x */ {if(LineTree[i].Node_Lpoint==x&&LineTree[i].Node_Rpoint==x) //如果为X格子 {LineTree[i].Node_Sum=LineTree[i].Node_Max=y; //单格子更新 while(i) //递归更新其所有父级路径 { if(i%2) {LineTree[i/2].Node_Sum=LineTree[i-1].Node_Sum+LineTree[i].Node_Sum; LineTree[i/2].Node_Max=fmax(LineTree[i-1].Node_Max,LineTree[i].Node_Max);} else { LineTree[i/2].Node_Sum=LineTree[i+1].Node_Sum+LineTree[i].Node_Sum; LineTree[i/2].Node_Max=fmax(LineTree[i+1].Node_Max,LineTree[i].Node_Max);} i/=2;}}else //递归遍历 {i*=2;if(x>LineTree[i].Node_Rpoint)updata(i+1,x,y);elseupdata(i,x,y);}}int readmax(int i,int x,int y) //区间最大值查询 {if(LineTree[i].Node_Lpoint==x&&LineTree[i].Node_Rpoint==y)_max=fmax(_max,LineTree[i].Node_Max); //只有一个数 else{ i*=2; if(x<=LineTree[i].Node_Rpoint) //右区间 { if(y<=LineTree[i].Node_Rpoint) //完全包含 readmax(i,x,y);else //半包含 readmax(i,x,LineTree[i].Node_Rpoint); } i++;if(y>=LineTree[i].Node_Lpoint) //左区间 { if(x>=LineTree[i].Node_Lpoint) //完全包含 readmax(i,x,y); else //半包含 readmax(i,LineTree[i].Node_Lpoint,y);}}}int readsum(int i,int x,int y) //区间和 遍历同上 {if(LineTree[i].Node_Lpoint==x&&LineTree[i].Node_Rpoint==y)_sum+=LineTree[i].Node_Sum;else { i*=2; if(x<=LineTree[i].Node_Rpoint) { if(y<=LineTree[i].Node_Rpoint) readsum(i,x,y);else readsum(i,x,LineTree[i].Node_Rpoint); } i++;if(y>=LineTree[i].Node_Lpoint) { if(x>=LineTree[i].Node_Lpoint) readsum(i,x,y); else readsum(i,LineTree[i].Node_Lpoint,y);}}} int main(){ int i,n,m,p,x,y; while(~scanf("%d%d",&n,&m)) { for(i=1;i<=n;i++) scanf("%d",&Box[i]);build(1,1,n); while(m--) { scanf("%d%d%d",&p,&x,&y); switch(p) { case 1:updata(1,x,y);break; case 2:_sum= 0;readsum(1,x,y);printf("%d\n",_sum);break; case 3:_max=-1;readmax(1,x,y);printf("%d\n",_max);break; }}}}
注意!!这个问题也是困扰了我很久的一个问题,我们申请线段树时候,其空间到底是多少?
对于线段树来说,N也就是单独的树最终会成为其叶子节点,二叉树所需要的节点是2n-1,那么我们能否认为线段树所需要的空间即2n-1?
然而实时证明,这是错误的。
我们假设构造(1,11)这个线段并对其节点进行标注
(1,11)【节点1】
(1,5) 【节点2】(6,11)【节点3】
(1,3) 【节点4】(4,5) 【节点5】 (6,8)【节点6】(9,11)【节点7】
(1,2) 【节点8】(3) 【节点9】(4) 【节点10】(5) 【节点11】(6,7)【节点12】(8)【节点13】(9,10)【节点14】(11)【节点15】
我们看,节点14也就是(9,10)还需要递归扩展子树
他的子树应该是2n,2n+1,也就是28,29
咦?11个数不应该是21个节点吗? 其实是这样的,在之前的叶子节点(比如8,9)虽然没有子节点了,但是他仍然占有16,17,18,19的位置
为了满足我们的查找规律。所以,我们申请资源的时候不应该设为2n-1
共同学习,写下你的评论
评论加载中...
作者其他优质文章