HDU1166(简单单点更新线段树)

将操作区间和点修改的位置和数字作为全局变量写起程序更加方便。

仅仅简单的单点更新不用使用下推函数和懒惰标记 

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int maxn=50005;
int tree[maxn<<2];
int num[maxn];
int n;
char str[7];
int L,C,R;//点修改的位置和修改数
//L,R也表示操作区间
void pushup(int rt)
{
    //pushup函数更新结点信息,这里是求和
    //rt是线段树中的结点值
    tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void build_tree(int l,int r,int rt)
{
    //l,r是区间端点,rt是映射在线段树中的结点
    if(l==r)//表示建树到达底层
    {
        tree[rt]=num[l];
        return;//到底层后函数返回
    }
    int m=(l+r)>>1;
    build_tree(l,m,rt<<1);
    build_tree(m+1,r,rt<<1|1);
    pushup(rt);
}
void update(int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt]+=C;
        //防止减成负数
        if(tree[rt]<0)
            tree[rt]=0;
        return;
    }
    int m=(l+r)>>1;
    if(L<=m)
        update(l,m,rt<<1);
    else
        update(m+1,r,rt<<1|1);
    pushup(rt);
}
//下推标记的函数
//如果只是单点修改,直接修改到底层即可,不用使用懒惰标记
int query(int l,int r,int rt)
{
    if(L<=l&&r<=R)
        return tree[rt];
    int m=(l+r)>>1;
    int ans=0;
    if(L<=m)
        ans+=query(l,m,rt<<1);
    if(m<R)
        ans+=query(m+1,r,rt<<1|1);
    return ans;
}
int main()
{
    int t;
    scanf("%d",&t);
    int T=t;
    memset(num,0,sizeof(num));
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
            scanf("%d",&num[i]);
        //建树
        build_tree(1,n,1);
        printf("Case %d:\n",T-t);
        while(scanf("%s",str))
        {
            if(str[0]=='E')
                break;
            else if(str[0]=='Q')
            {
                //线段树查询
                scanf("%d%d",&L,&R);
                printf("%d\n",query(1,n,1));
            }
            else if(str[0]=='A')
            {
                //线段树点修改增加
                scanf("%d%d",&L,&C);
                update(1,n,1);
            }
            else if(str[0]=='S')
            {
                //线段树点修改减少
                scanf("%d%d",&L,&C);
                C*=-1;
                update(1,n,1);
            }
        }
    }
    return 0;
}

查询语句中的几条语句有些难理解,需要好好琢磨,如下所示:

    int ans=0;
    if(L<=m)
        ans+=query(l,m,rt<<1);
    if(m<R)
        ans+=query(m+1,r,rt<<1|1);
    return ans;

这里语句的含义是判断左右子树和[L,R]这个操作区间有没有交集,有交集就进行递归。

左子树的区间是[l,m],判断条件是判定m是否大于L。

右子树的区间是[m+1,r],判断条件是判定m是否小于R。

我们初始的[l,r]取值足够合适,不会出现l>R(越界)的情况?

证明:【向前考虑条件:】

假设l>R,则应当来源于递归时传入参数的上述代码的两种情况。

首先判定传参的第一种情况:L<=m时,此时的l应当是传参之前的l,一直递归向上考虑,如果一直是第一种传参情况,l初始值为1,不可能发生l>R的情况。

其次判定传参的第二种情况:m<R时,此时的l应当是传参之前的m+1,由于限制条件是m<R,则传参之前的m+1==传参之后的l,此时的l的最大值为(m)max+1==R,不会出现l>R的情况。

【向后考虑影响:】

如前所述,向后传参时如果一直是第一种情况,那么不会发生l>R的越界问题;

如果出现第二种传参时,传参后区间为[l,r],m=(l+r)>>1,

若m==l,则出现遍历到底层应当返回函数值的情况,

若m>l,此时m>R,所以二层传参时不会出现第二种传参情况了,只会出现第一种,则l一直不变,直到出现m==l为止,

证明完毕。

r<L越界的情况证明相似。

结论,我们的代码最终总会将[l,r]这个当前区间控制在操作区间[L,R]之间,而不会出现r<L或者l>R这两种越界的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值