这道题是线段树的 单点更新和区间查询最大值
(AC代码在最后)
通过这道题 理解下线段树
线段树的原理
关于原理 网上有很多讲解 大家可以自己学习 这里不单独赘述原理
而是结合代码讲解下 原理也不难 相信大家看了代码仔细看下边的解释可以理解
另外 推荐两篇聚聚写的博客 写的很详细~~~~(但是讲的很深入很系统 自行选择重点 反正我看的头晕)
然后下面主要讲下代码 都是个人理解 不足请指出
基本结构
struct NODE{
int value,left,right;
}node[maxnode];
int father[maxn];
node是线段树的结构 每个节点代表一个区间 left 和right代表区间范围 value在这道题中代表区间的最大值
father数组用来记录叶子节点在线段树中的位置 这些不太懂不要紧 下边会解释
建树
建树采用的是顺序存储 存储关系是 左孩子=父亲x2 右孩子等于父亲x2+1
举个例子 给你区间 1-5的数组 分别是 1 2 3 4 5
则建立的线段树 如下图所示 红色的是每个点在这棵树中的节点编号 比如1号代表的就是区间【1,5】
[1,5]1 | |||||||||
[1,2] 2 [3,5] 3 | |||||||||
4 [1,1] [2,2] 5 [3,4] 6 [5,5]7 | |||||||||
8 9 10 11 [3,3]12 [4,4]13 |
而叶子节点代表的就是数字本身 如12号 区间【3,3】 就是原始数据中的3号
在本题中 每个节点代表的是该区间的最大值(有的题也可以代表区间和)
所以 各个叶子节点的值就是 所代表的节点的值
而father数组则是用来记录原始数据中各个点在线段树中对应的叶子节点的编号 如 father【3】=12;
建树代码及注释
void BuildTree(int i,int left,int right){
node[i].left=left;//i是各个节点在线段树中的位置编号
node[i].right=right;
node[i].value=0;
if(left==right){//到了叶子节点
father[left]=i;return ;//left就是此叶子节点所代表的原数据的编号
}
BuildTree(i<<1,left,(left+right)/2);
BuildTree((i<<1)+1,((left+right)/2)+1,right);
}
单点更新
单点更新是自下而上的
比如说 我们要把 1 2 3 4 5 中的3 更新为6
那么 首先 我们通过刚才的father数组可以知道 3 存在线段树中的12号位置 并且叶子节点代表的区间就是一个点
所以我们直接 使 node[ father[ 3 ] ].value=6 就修改了3号的值
然后 我们通过 12/2 得到了 他的父亲节点6 而通过6*2 和6*2+1 得到了12 和13 更新后的两个孩子节点
取两个中的最大值 则完成了6号节点的更新 以此类推 向上更新 直到根节点
代码
void UpdateTree(int ri){
if(ri==1)return ;
int fi=ri/2;
int a=node[fi<<1].value;
int b=node[(fi<<1)+1].value;
node[fi].value=max(a,b);
UpdateTree(ri/2);//向上递归更新
}
区间查询最大值
区间查询与单点更新相反 区间查询是自上而下的
比如 如果要查询【1,5】区间的最大值 那就直接是 node【1】的值
而如果要查询区间【3,4】的最大值的话 就需要自上而下查询
首先【1,5】分为左孩子【1,3】和右孩子【4,5】 发现所查询区间在两个区间中都有涉及
那么就查询左孩子中【3,3】右孩子中【4,4】 取其最大值
所以 整个流程就是 判断所查区间否完全在节点区间范围内 若在 则往下查 不在 则只查所需要的区间
具体如何判断 看代码
void Query(int i,int l,int r){
if(node[i].left==l&&node[i].right==r){//与所查区间完全重合
Max=max(Max,node[i].value);return ;
}
i=i<<1;//判断左孩子
if(l<=node[i].right){//涉及左孩子
if(r<=node[i].right)Query(i,l,r);//完全在左孩子里
else Query(i,l,node[i].right);//不完全在
}
i++;//右孩子
if(r>=node[i].left){//涉及右孩子
if(l>=node[i].left) Query(i,l,r);//完全在右孩子里
else Query(i,node[i].left,r);//不完全右孩子
}
}
以上是线段树单点更新和区间最大值查询的原理及理解
下面是hdu1754题解代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2e6+7;
const int maxnode=1<<19;
struct NODE{
int value,left,right;
}node[maxnode];
int father[maxn];
int n,m,g,x,y;
void BuildTree(int i,int left,int right){
node[i].left=left;//i是各个节点在线段树中的位置编号
node[i].right=right;
node[i].value=0;
if(left==right){//到了叶子节点
father[left]=i;return ;//left就是此叶子节点所代表的原数据的编号
}
BuildTree(i<<1,left,(left+right)/2);
BuildTree((i<<1)+1,((left+right)/2)+1,right);
}
void UpdateTree(int ri){
if(ri==1)return ;
int fi=ri/2;
int a=node[fi<<1].value;
int b=node[(fi<<1)+1].value;
node[fi].value=max(a,b);
UpdateTree(ri/2);//向上递归更新
}
int Max;
void Query(int i,int l,int r){
if(node[i].left==l&&node[i].right==r){//与所查区间完全重合
Max=max(Max,node[i].value);return ;
}
i=i<<1;//判断左孩子
if(l<=node[i].right){//涉及左孩子
if(r<=node[i].right)Query(i,l,r);//完全在左孩子里
else Query(i,l,node[i].right);//不完全在
}
i++;//右孩子
if(r>=node[i].left){//涉及右孩子
if(l>=node[i].left) Query(i,l,r);//完全在右孩子里
else Query(i,node[i].left,r);//不完全右孩子
}
}
int main(){
ios::sync_with_stdio(false);
while(cin>>n>>m){
BuildTree(1,1,n);
for(int i=1;i<=n;i++){
cin>>g;
node[father[i]].value=g;
UpdateTree(father[i]);
}
while(m--){
string op;
cin>>op>>x>>y;
if(op[0]=='Q'){
Max=0;
Query(1,x,y);
cout<<Max<<endl;
}
else{
node[father[x]].value=y;
UpdateTree(father[x]);
}
}
}
return 0;
}
再加一个练习题 链接 nyoj116