POJ 3468 A Simple Problem with Integers-利用lzy进行区间更新的线段树

You have N integers, A1,A2,...,AN . You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

初学线段树,看了两天终于找到了一点感觉,自己写了一个板子,修修改改竟然也AC了三道题,决定把心得和代码写进博客,有错误的地方还请指出来哦(如果有人看的话)。第一次写博客,可能会很啰嗦0.0。

线段树的思想,在一棵二叉树上,每一层都是一个完整的区间,随着层数加深,每个区间不断被划分为两个更小的区间,分别交给两个子结点维护。对某一范围的查询,只需要少数内部结点维护的区间就包含了这个范围的所有信息。

题意:含N个整数 A1,A2,...,An 的序列,对其有Q次询问,每次询问都是下面两个操作的一个
Q l r 求 Al ~ Ar 所有整数的和
C l r c 将 Al ~ Ar 所有整数加c

这道题看上去并不复杂,主要就是卡查询和更新整个树的时间,也就是说要尽量减少每次查询和更新的基本操作次数。

查询操作很好处理,主要是处理区间结点的值的更新很容易超时。如果每次对所有区间结点更新,可能将近一半的更新其实都没用到,因为很多查询,用离根结点较近的区间结点就能组合出答案,不需要访问到的底层结点。所以可以偷个懒,从根节点往下更新时,遇到一个区间结点就把它的lzy设置为要增加的值,就把它以及它的所有子节点的更新停在lzy上面,等到下次需要查询它的子节点的时候,再继续向要查询的那个结点更新。

以[1~5]的线段树作为例子解释一下三个函数:
ll build(int l, int r , int i)
1.从根结点开始,不断的把大区间分成左右两个子区间,交给两个子结点处理。
2.在遇到叶子节点时进行一次输入(后序遍历使得每个输入放到恰当的位置)。每个结点设置完以后返回的是它的区间的和。

build以后的情况
ll qsum(int ql, int qr, int i )
1.首先查看结点i是否有lzy,如果有就更新结点i的sum,并把lzy传递给子结点。
2.把范围的求和分散到区间上。从根节点开始,比较范围[ql, qr] 和结点i对应的区间[l, r]。如果[l, r]不是[ql, qr],就根据[ql, qr]在[l, r]的左半部分、中间还是右半部分,在i的子结点中继续寻找。因为[l, r]包含了[ql , qr],一定能找出一个子结点的组合把范围和[ql, qr]求出来。
如果觉得难以想象的话可以把代码中//#define TEST //测试模式这句的注释去掉,程序会打印每次寻找时经过的区间

比如求 Q 1 4 时
先和[1~5]比较,然后将[1,4]分为[1,3]和[4,4],在左子结点中寻找[1,3],在又子结点中寻找[4,4]。然后在[4,5]的左子结点继续寻找[4,4]。
qsum的经过

ll cadd(int cl,int cr,int c int i)
1.和qsum有点类似,要把范围上的更新分散到区间结点上,但有一点不一样,不仅要找出分散到哪些区间上,这些区间的父区间也应更新。
比如执行C 1 4 1时,不仅[1,3]和[4,4]需要更新,[1,5]和[4,5]也需要更新。其实就是把经过的所有区间的sum更新就行了 。需要注意的是,[1,3]和[4,4]的更新体现在把lzy设置为要增加的值1,而父结点的更新直接更新sum就行了。
下面是ac代码
宏left(i) right(i) 分别得到代表结点的左右子结点的编号
mid(l,r)得到区间[l,r]的中点

#include <stdio.h>
#define maxn 100005
typedef long long ll;
//#define TEST //测试模式

#define left(i) (i<<1)
#define right(i) ((i<<1)+1)
#define mid(l,r) ((l) + (((r) -(l)) >> 1))

int N, Q;
class xds
{

public:
    xds() :lzy(0), sum(0), l(0), r(0) {}
    ll lzy;//等待更新此结点的sum和它字结点的lzy
    ll sum;
    int l;
    int r;
};
#ifdef TEST
xds node[30];
#else
xds node[maxn << 2];
#endif // TEST
void renew(int i) {
    if (node[i].l == node[i].r) {
        node[i].sum += node[i].lzy;
        node[i].lzy = 0; return;
    }

    node[i].sum += (node[i].r - node[i].l + 1)*node[i].lzy;
    node[left(i)].lzy += node[i].lzy;
    node[right(i)].lzy += node[i].lzy;
    node[i].lzy = 0;
}

ll build(int l, int r, int i) {
#ifdef TEST
    printf("访问区间%d %d\n", l, r);
#endif // TEST

    node[i].l = l;
    node[i].r = r;
    if (r == l) {
        scanf("%lld", &node[i].sum);
        return node[i].sum;
    }
    return node[i].sum = build(l, mid(l,r), left(i)) + build(mid(l,r) + 1, r, right(i));
}

ll qsum(int ql, int qr, int i) {
    ll temp;
    int l = node[i].l;
    int r = node[i].r;
#ifdef TEST
    printf("访问区间%d %d\n", l, r);
#endif // TEST

    if (node[i].lzy)
        renew(i);
    if (l == ql&&r == qr) {
        return node[i].sum;
    }
    if (qr <= mid(l, r))
    {
        return qsum(ql, qr, left(i));
    }

    if (ql <= mid(l, r))
    {
        temp = qsum(ql, mid(l, r), left(i)) + qsum(mid(l, r) + 1, qr, right(i));
        return temp;
    }
    temp = qsum(ql, qr, right(i));
    return temp;

}
void cadd(int cl, int cr, ll c, int i) {
    ll temp;
    int l = node[i].l;
    int r = node[i].r;
#ifdef TEST
    printf("访问区间%d %d\n", l, r);
#endif // TEST

    if (cl == l&&cr == r) {
        node[i].lzy += c; return;
    }
    node[i].sum += (cr - cl + 1)*c;
    if (cr <= mid(l, r))
    {

        cadd(cl, cr, c, left(i));
        return;
    }

    if (cl <= mid(l, r))
    {
        cadd(cl, mid(l, r), c, left(i));
        cadd(mid(l, r) + 1, cr, c, right(i));

        return;
    }
    cadd(cl, cr, c, right(i));
    return;
}


int main() {

#ifdef TEST
    printf("数据量N 询问次数Q: ", maxn);
#endif //TEST
    scanf("%d %d", &N, &Q);

#ifdef TEST
    printf("建立%d个数据的线段树,输入%d个数据: \n", N, N);
#endif //TEST

    build(1, N, 1);
#ifdef TEST
    printf("建树完毕,输入Q l r求区间和,输入C l r c增加区间内所有数,输入E退出\n");
#endif // TEST
    char temp[2];
    ll res;
    int l, r;
    ll c;
    for (int kase = 0; kase < Q; ++kase) {
        scanf("%s", &temp);
        if (temp[0] == 'Q') {
            scanf("%d %d", &l, &r);
            res = qsum(l, r, 1);
#ifdef TEST
            printf("求和结果为: ");
#endif // TEST
            printf("%lld\n", res);
        }
        else if (temp[0] == 'C') {
            scanf("%d %d %lld", &l, &r, &c);
            cadd(l, r, c, 1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值