题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1754
Problem Description
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
Output
对于每一次询问操作,在一行里面输出最高成绩。
Sample Input
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
Sample Output
5
6
5
9
给一个1到n的一个区间,可以进行单点更新和求区间最大值的操作。
贴一个线段树比较全面的博客:https://blog.csdn.net/u012469987/article/details/41357377
线段树的作用,其实是时间的优化,它的作用和功能并没有普通维护数组的方法好,但是它的时间复杂度从n降到了logn。
struct node
{
int l,r,w;
};
node tree[Max];
void build(int k,int l,int r)
{
tree[k].l = l; tree[k].r = r;
if(l==r){
tree[k].w = a[l];
return;
}
int mid = (l+r)/2;
build(k*2,l,mid);
build(k*2+1,mid+1,r);
tree[k].w = max(tree[k*2].w,tree[k*2+1].w);
}
有的线段树代码只用一个数组来表示,每次的左右位置都要重复计算。实际上,每次递归到达节点它的左右位置一定是一样的,这里声明了node节点,将区间最大值w,和该区间为左边界l到右边界r记录下来,以后每次update或者是query在参数中就少写两个参数表示区间边界的l和r了。
在建树的过程中,k表示当前节点的位置,l和r表示当前节点维护最大值的左右边界。当l==r,即为叶子节点时,将初始值赋给叶子(数组a[n]存储了初始值)。然后递归调用build,建立左右子树。
void update(int k,int p,int v)
{
if(ptree[k].r) return;
if(tree[k].l==tree[k].r&&tree[k].r==p){
tree[k].w = v;
return;
}
update(k*2,p,v);
update(k*2+1,p,v);
tree[k].w = max(tree[k*2].w,tree[k*2+1].w);
}
k表示节点的编号,将位置p的值改为v。
如果位置p不在当前节点维护的范围内,自然没有什么作用了,直接return。当找到该叶子节点,将其值改变。和建树一样,每次都是要先递归调用,当下层维护好,在回溯的过程中,再把上层的维护。
int query(int k,int l,int r)
{
if(rtree[k].r) return 0;
if(l<=tree[k].l&&tree[k].r<=r) return tree[k].w;
return max(query(k*2,l,r),query(k*2+1,l,r));
}
查询区间l到r的最大值。和更新操作有些相似,如果当前节点维护的区间不在查询的范围内,是不影响我们要的区间的最大值的,直接return 0。如果完全在当前的范围内,就返回当前节点维护的最大值。不是上述两种情况,就要再把区间再细分。最后分的结果,其实是把我们查询的区间分成了好几个小区间,回溯的过程中,比较这些小区间的最大值。
总的来说,还是要好好打牢二叉树的基础= = 。至于开的区间为什么要乘4,我忘了是哪篇博客了,说你自己画一下区间长度为10的线段树,就知道为什么乘4了。画了确实明白了,其实是乘了两次2,第一次乘2是得到的是二叉树最后一层节点的个数(有这种可能,满二叉树的基础上,不幸刚好多出了几枝,但是还是要按最后多的一层给空间大小),再乘2是得到总二叉树节点的大小。忘记乘4就会报错Runtime Error(ACCESS_VIOLATION),数组开太小了。
#include
#include
#include
#include
#include