• AcWing 243. 一个简单的整数问题2


    \(AcWing\) \(243\). 一个简单的整数问题2

    一、题目描述

    给定一个长度为 \(N\) 的数列 \(A\),以及 \(M\)条指令,每条指令可能是以下两种之一:
    C l r d,表示把 \(A[l],A[l+1],…,A[r]\) 都加上 \(d\)
    Q l r,表示询问数列中第 \(l∼r\)个数的和。

    对于每个询问,输出一个整数表示答案。
    输入格式
    第一行两个整数 \(N,M\)

    第二行 \(N\)个整数 \(A[i]\)

    接下来 \(M\) 行表示 \(M\)条指令,每条指令的格式如题目描述所示。

    输出格式
    对于每个询问,输出一个整数表示答案。

    每个答案占一行。

    数据范围

    \(1≤N,M≤105\),\(|d|≤10000,|A[i]|≤10^9\)

    二、树状数组

    树状数组解决的问题 操作对象 例题
    单点修改,区间查询 前缀和 点我
    区间修改,单点查询 一个差分数组 点我
    区间修改,区间和查询 两个差分数组 本题

    区间修改,单点查询

    如果是区间修改,单点查询。只需用树状数组维护一个差分数组\(b\),假设查询位置\(x\),那么\(\displaystyle \sum_{i=1}^{x}b_i\)就是\(x\)位置上的变化后的值。

    区间修改+区间和查询

    考虑引入区间查询。首先最暴力想,假设查询\([1,r]\)。那么\([1,r]\)的答案=\(\displaystyle \sum_{i=1}^{r}\sum_{j=1}^{i}b_j\)
    不妨举个特例,更直观些。假设查询\([1, 4]\)。那么\(ans=(b_1)+(b_1+b_2)+(b_1+b_2+b_3)+(b_1+b_2+b_3+b_4)=4b_1+3b_2+2b_3+1b_4\)

    换成查询\([1, r]\)。那么\(\displaystyle ans=(r+1-1)b_1+(r+1-2)b_2+(r+1-3)b_3+…+(r+1-r)b_r = (r+1)\sum_{i=1}^{r}b_i-\sum_{i=1}^{r}i*b_i\)

    显然第一项用树状数组\(tr1\)维护\(b\)数组可求出,第二项求不出。令\(c=i*b[i]\),新开一个树状数组\(tr2\)维护\(c\)就行了。

    三、实现代码

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1000010;
    typedef long long LL;
    
    int n, m; // n个元素,m次操作
    int a[N]; //原始数组
    
    LL tr1[N], tr2[N]; //① 保存基底数组为原数组差分数组的树状数组 ② i*b[i]的前缀和数组
    
    //树状数组模板
    #define lowbit(x) (x & -x)
    
    void add(int x, int c) {
        for (int i = x; i <= n; i += lowbit(i)) tr1[i] += c, tr2[i] += (LL)x * c;
    }
    
    LL sum(int x) {
        LL res = 0;
        for (int i = x; i; i -= lowbit(i)) res += (LL)(x + 1) * tr1[i] - tr2[i];
        return res;
    }
    
    int main() {
        //加快读入
        ios::sync_with_stdio(false), cin.tie(0);
        cin >> n >> m;
    
        int x, y, k;
        string op;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            add(i, a[i] - a[i - 1]); //保存基底是差分数组的树状数组
        }
        for (int i = 1; i <= m; i++) {
            cin >> op >> x >> y;
            if (op[0] == 'C') {
                cin >> k;
                add(x, k), add(y + 1, -k); //维护差分
            } else                         //查询
                printf("%lld\n", sum(y) - sum(x - 1));
        }
        return 0;
    }
    

    四、线段树+区间修改+懒标记+区间和查询

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int N = 100010;
    
    int n, m;
    int w[N];
    struct Node {
        int l, r;
        LL sum, tag; //区间总和,修改的数值(懒标记)
    } tr[N << 2];
    
    //向祖先节点更新统计信息
    void pushup(int u) {
        tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum; //向父节点更新sum和
    }
    
    //父节点向子孙节点传递信息
    void pushdown(int u) {
        auto &root = tr[u], &ls = tr[u << 1], &rs = tr[u << 1 | 1];
        if (root.tag) { //如果存在懒标记
            // tag传递到子段,节段的sum和需要按 区间长度*root.tag 进行增加
            ls.tag += root.tag, ls.sum += (LL)(ls.r - ls.l + 1) * root.tag;
            rs.tag += root.tag, rs.sum += (LL)(rs.r - rs.l + 1) * root.tag;
            //清除懒标记
            root.tag = 0;
        }
    }
    
    //构建
    void build(int u, int l, int r) {
        tr[u] = {l, r, 0, 0};        //标记范围
        if (l == r) {                //叶子
            tr[u] = {l, r, w[l], 0}; //区间内只有一个元素l(r),区间和为w[l],不需要记录向下的传递tag
            return;
        }
    
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); //左右儿子构建
        pushup(u);                                            //通过左右儿子构建后,向祖先节点反馈统计信息变化
    }
    
    //以u为根,在区间[l,r]之间全都增加d
    void modify(int u, int l, int r, int d) {
        if (tr[u].l >= l && tr[u].r <= r) {
            tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d; //总和增加
            tr[u].tag += d;                               //懒标记+d
            return;
        }
        pushdown(u); // 如果自己身上有旧的tag数值,在递归前需要将原tag值pushdown到子孙节点去
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, d);    //与左区间有交集
        if (r > mid) modify(u << 1 | 1, l, r, d); //与右区间有交集
        pushup(u);                                //将结果的变更更新到祖先节点
    }
    
    //关键的查询操作
    LL query(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
        /*懒标记的思想:在更新时只标识不修改,有查询时现用现改的思想。这么做的目的当然是怕有多组修改,
        就需要修改多次,而且路径太长不划算。如果只标识不修改,直接返回,当然快了。比如修改了3次后查询一次,
        也只是真正修改了一次,降低了修改次数。
        */
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        LL sum = 0;
        if (l <= mid) sum = query(u << 1, l, r);
        if (r > mid) sum += query(u << 1 | 1, l, r);
    
        //左查+ 右查 = 总和
        return sum;
    }
    
    int main() {
        //加快读入
        ios::sync_with_stdio(false), cin.tie(0);
        cin >> n >> m;
        for (int i = 1; i <= n; i++) cin >> w[i];
        //构建线段树
        build(1, 1, n);
    
        char op;
        int l, r, d;
        while (m--) {
            cin >> op >> l >> r;
            if (op == 'C') {
                cin >> d;
                modify(1, l, r, d); //区间修改
            } else
                printf("%lld\n", query(1, l, r)); //区间查询
        }
        return 0;
    }
    
  • 相关阅读:
    iOS10---新特性以及适配点
    linux下,MySQL默认的数据文档存储目录为/var/lib/mysql。
    Linux安装JDK完整步骤
    ajax获取json数据为undefined--原因解析
    怎样用jQuery拿到select中被选中的option的值
    一个Filter需要配置多个url-pattern
    Jquery+Ajax实现Select动态添加数据
    tomcat启动报错:Address already in use: JVM_Bind
    Gson 是google解析Json的一个开源框架,同类的框架fastJson,JackJson
    Java中文乱码解决方案
  • 原文地址:https://www.cnblogs.com/littlehb/p/16143694.html
Copyright © 2020-2023  润新知