【题解】A Simple Problem with Integers POJ - 3468 (线段树成段更新 模板题)⭐⭐⭐

A Simple Problem with Integers POJ - 3468

Psy给出了一个序列,Drh需要处理如下两种询问。

"C a b c"表示给[a, b]区间中的值全部增加c (-10000 ≤ c ≤ 10000)。

“Q a b” 询问[a, b]区间中所有值的和。

Input

第一行包含两个整数N, Q。1 ≤ N,Q ≤ 100000.

第二行包含n个整数,表示初始的序列A (-1000000000 ≤ Ai ≤ 1000000000)。

接下来Q行询问,格式如题目描述。

Output

对于每一个Q开头的询问,你需要输出相应的答案,每个答案一行。

Examples

Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15

Hint




概述

之前有提到的基于RMQ和sum的线段树都是单点更新, 也就是从根节点开始走, 一直递归到对应的叶子节点
但对于一类要求区间更新的问题, 如果仍然以单点更新的逻辑去循环递归, 就会使得复杂度骤增, 极端情况可以达到NlogN.

lazy-tag思想算法

lazy-tag思想: 记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。
例如, 我们现在对[a,b]区间进行+c操作, 依然从根节点[1,n]开始递归执行update
如果刚好执行到一个子节点[l, r]使得x<=l&&y>=r, 也就是该区间完全包含在[x, y]种时, 我们依照update一样更新其值
重点在于, 此时我们不再更新其子节点的值, 就此return, 直至下次使用到的时候再去更新, 可以避免很多重复的操作.


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const int inf = 1 << 30;
const LL maxn = 1e5 + 10;

int N, Q, a[maxn];
LL sum[maxn*4], add[maxn*4];
void pushup(int id) {   //向上更新
    sum[id] = sum[id << 1] + sum[id << 1 | 1];
}
void pushdown(int id, int m){
    if(add[id]){
        add[id<<1] += add[id];
        add[id<<1|1] += add[id];         //更新左右儿子的add
        sum[id<<1] += (m-(m>>1))*add[id];//更新sum[id]
        sum[id<<1|1] += (m>>1)*add[id];
        add[id] = 0;
    }
}
void build(int id, int l, int r) {
    add[id] = 0;
    if(l == r) {
        sum[id] = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(id << 1, l, mid);
    build(id << 1 | 1, mid + 1, r);
    pushup(id);
}
void update(int id, int l, int r, int x, int y, int c) {
    if(x<=l && y>=r){
        add[id] += c;
        sum[id] += (LL)c*(r-l+1); //r-l+1为原中点
        return;                   //lazy操作
    }
    pushdown(id, r-l+1);          //使用到时再向下更新
    int mid = (l+r)>>1;
    if(x <= mid)
        update(id << 1, l, mid, x, y, c);
    if(y > mid)
        update(id << 1 | 1, mid + 1, r, x, y, c);
    pushup(id);
}
LL query(int id, int l, int r, int x, int y) {
    if(x <= l && y >= r)
        return sum[id];
    pushdown(id, r-l+1);           //使用到时再向下更新
    int mid = (l + r) >> 1;
    LL ret = 0;
    if(x <= mid)
        ret += query(id << 1, l, mid, x, y);
    if(y > mid)
        ret += query(id << 1 | 1, mid + 1, r, x, y);
    return ret;
}

int main() {
    scanf("%d%d", &N, &Q);
    for(int i = 1; i <= N; i++)
        scanf("%d", &a[i]);
    ms(sum, 0);
    build(1, 1, N);
    char opt;
    int a, b, c;
    while(Q--) {
        scanf(" %c", &opt);
        if(opt == 'Q') {
            scanf("%d%d", &a, &b);
            printf("%lld\n", query(1, 1, N, a, b));
        } else if(opt == 'C') {
            scanf("%d%d%d", &a, &b, &c);
            update(1, 1, N, a, b, c);
        }
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值