POJ 3468 A Simple Problem with Integers(线段树的区间更新)(做题一知半解的结果就是什么也学不会)

题目POJ - 3468

做这道题之前一直以为会用单点更新就行了,区间更新也可以转化为单点更新的问题,结果当然是超时

之后就是学习的过程,看了好长时间算是明白了区间更新的奥妙,即pushdown函数实现了标记下移更新

再更新一下,之前一直以为自己理解了,其实上面说的理解是一知半解,因为只有一个地方不懂而已,没有深究,后来发现其实自己一直都理解的有偏差,还是太急于求成了

之前一直不知道为什么在update里面用过pushdown了,而在query中还要再用一次,其实Lazy思想恰恰是暂时不更新rt子节点的sum[]值,到此就return,直到下次需要用到rt子节点的值的时候才去更新,这样避免许多可能无用的操作,从而节省时间,即在update中设置标记,在query中完成最终更新

Lazy标记还有一个奇妙之处就是他是可以叠加的,只要初始标记为0,各种标记都可以进行叠加

其中

PushUp(rt):通过当前节点rt把值递归向上更新到根节点

PushDown(rt):通过当前节点rt递归向下去更新rt子节点的值

rt表示当前子树的根,也就是当前所在的结点

为何先用pushdown再用pushup,就是因为先将懒惰标记移到字节点,在能保证下面的更新正确,之后再用pushup更新一下新的端点和。。。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<cmath>
using namespace std;
#define maxn  100005
long long sum[maxn << 2];//四倍数组存线段树
long long laz[maxn << 2];
void pushup(int rt)//常规更新操作
{
    sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void pushudown(int rt, int m)//区间更新核心操作,标记下移,rt为父节点,m为当前区间长度
{
    if(laz[rt])//如果此处有标记(即非0)
    {
        sum[rt << 1] += (m-(m >> 1))*laz[rt];//更新子左端点值,即等于下一个区间元素和*lazy值
        sum[rt << 1 | 1] += (m >> 1)*laz[rt];//更新子右端点值
        laz[rt << 1] += laz[rt];//lazy标记下移
        laz[rt << 1 | 1] += laz[rt];//同上
        laz[rt] = 0;//清除父节点标记
    }
}
void build(int l, int r, int rt)//常规建树
{
    if(l == r)
    {
        scanf("%lld",&sum[rt]);
        return ;
    }
    int m = (l + r) >> 1;
    build(l, m, rt << 1);
    build(m + 1, r, rt << 1 | 1);
    pushup(rt);
}
void update(int left, int right, int c, int l, int r, int rt)
{
    if(left <= l && right >= r)//更新lazy标记和线段树端点,遇到第一个符合条件的区间就return,而不是一直执行到末尾
    {
        laz[rt] += c;
        sum[rt] += c*(r - l + 1);
        return ;
    }
    pushudown(rt, r-l+1);//如果当前区间不是想找的区间,继续向下的时候需要用到pushup(),此时putdown是为了配合pushup使用
    int m = (l + r) >> 1;
    if(left <= m)update(left, right, c, l, m, rt << 1);
    if(right > m)update(left, right, c, m + 1, r, rt << 1 | 1);
    pushup(rt);
}
/*
单点更新模式
void update(int p, int add, int l, int r,int rt)//更新数据,需要更新的是包含p元素的节点,需要更新的值为add  
{  
    if(l == r)//递归到线段树最后一层,此时每个区间只包含一个点  
    {  
        sum[rt] += add;  
        return ;  
    }  
    int m = (l + r) >> 1;  
    if(p <= m)update(p, add, l, m, rt << 1);//需要更新的地方  
    else update(p, add, m + 1, r, rt << 1 | 1);  
    pushup(rt);//更新  
}  
*/
long long query(int left, int right, int l, int r, int rt)//常规查询,多了一个pushdown()
{
    if(left <= l && right >= r) return sum[rt];
    pushudown(rt, r - l + 1);//此处继续上面的未完成的pushdown操作
    int m = (l + r) >> 1;
    long long ans = 0;
    if(left <= m) ans += query(left, right, l, m, rt << 1);
    if(right > m) ans += query(left, right, m + 1, r, rt << 1 | 1);
    return ans;
}
int main()
{
    int m, n;
    scanf("%d%d",&n,&m);
    memset(laz, 0, sizeof(laz));//初始无lazy标记
    build(1, n, 1);
    while(m --)
    {
        char ch;
        int a, b, c;
        getchar();//吸收回车
        scanf("%c",&ch);
        if(ch == 'Q')
        {
            scanf("%d%d",&a,&b);
            printf("%lld\n",query(a,b,1,n,1));
        }
        else
        {
            scanf("%d%d%d",&a,&b,&c);
            update(a, b, c, 1, n, 1);//更新区间(a,b)的值,更新值为c
        }
    }
    return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值