HDU1754-I Hate It(线段树 单点更新,区间查询最大)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1754

Problem Description

很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。

Input

本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N M ( 0,分别代表学生的数目和操作的数目。学生ID编号分别从1编到N。第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表IDi的学生的成绩。接下来有M行。每一行有一个字符 C (只取'Q''U') ,和两个正整数AB
C'Q'的时候,表示这是一条询问操作,它询问IDAB(包括A,B)的学生当中,成绩最高的是多少。
C'U'的时候,表示这是一条更新操作,要求把IDA的学生的成绩更改为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 
#include 
#include 
#define  ll long long
using namespace std;

const int Max = 200010*4;

struct node
{
    int l,r,w;
};
node tree[Max];
int a[Max],n,m,t,x,y;
char ch;

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);
}

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);
}

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));
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(tree,0,sizeof(tree));
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&t);
            a[i] = t;
        }
        build(1,1,n);
        for(int i=1;i<=m;i++)
        {
            scanf(" %c%d%d",&ch,&x,&y);
            if(ch=='Q') {printf("%d\n",query(1,x,y));}
            else if(ch=='U') update(1,x,y);
        }
    }
}

 

 

你可能感兴趣的:(线段树/树状数组)