侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 674 篇文章
  • 累计创建 128 个标签
  • 累计收到 20 条评论

目 录CONTENT

文章目录

【树状数组, 线段树】动态求连续区间和

GabrielxD
2023-03-06 / 0 评论 / 0 点赞 / 165 阅读 / 1,780 字
温馨提示:
本文最后更新于 2023-03-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

题目

1264. 动态求连续区间和 - AcWing题库


给定 nn 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b][a,b] 的连续和。

输入格式

第一行包含两个整数 nnmm ,分别表示数的个数和操作次数。

第二行包含 nn 个整数,表示完整数列。

接下来 mm 行,每行包含三个整数 k,a,bk,a,bk=0k = 0 ,表示求子数列 [a,b][a,b] 的和; k=1k=1 ,表示第 aa 个数加 bb )。

数列从 11 开始计数。

输出格式

输出若干行数字,表示 k=0k = 0 时,对应的子数列 [a,b][a,b] 的连续和。

数据范围

1n1000001 \le n \le 100000 ,
1m1000001 \le m \le 100000
1abn1 \le a \le b \le n ,
数据保证在任何时候,数列中所有元素之和均在 int 范围内。

输入样例:

10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8

输出样例:

11
30
35

解题

方法一:树状数组

树状数组

这里仅介绍树状数组的简单实现,详细介绍和证明见树状数组 - OI Wiki

简介

树状数组/二元索引树/Fenwick树是一种支持 单点修改区间查询 的,代码量小的数据结构。

作用
  1. 单点修改( O(logn)O(\log{n}) ):把某个位置上的数 自增 一个值。
  2. 区间查询( O(logn)O(\log{n}) ):求序列中的某个 前缀和
实现

image-20230306190750305

c[1]=a[1]c[2]=c[1]+a[2]c[3]=a[3]c[4]=c[2]+c[3]+a[4]c[5]=a[5]c[6]=c[5]+a[6]c[7]=a[7]c[8]=c[4]+c[6]+c[7]+a[8]c[9]=a[9]c[10]=c[9]+a[10]c[11]=a[11]c[12]=c[10]+c[11]+a[12]c[13]=a[13]c[14]=c[13]+a[14]c[15]=a[15]c[16]=c[8]+c[12]+c[14]+c[15]+a[16]\begin{aligned} c[1] &= a[1] &&\\ c[2] &= c[1] + a[2] &&\\ c[3] &= a[3] &&\\ c[4] &= c[2] + c[3] + a[4] &&\\ c[5] &= a[5] &&\\ c[6] &= c[5] + a[6] &&\\ c[7] &= a[7] &&\\ c[8] &= c[4] + c[6] + c[7] + a[8] &&\\ c[9] &= a[9] &&\\ c[10] &= c[9] + a[10] &&\\ c[11] &= a[11] &&\\ c[12] &= c[10] + c[11] + a[12] &&\\ c[13] &= a[13] &&\\ c[14] &= c[13] + a[14] &&\\ c[15] &= a[15] &&\\ c[16] &= c[8] + c[12] + c[14] + c[15] + a[16] &&\\ \end{aligned}

lowbit(n)lowbit(n) 可以获取 nn 的二进制表示中最低位的 1 。若 xx 的二进制表示中低位连续的 00 的个数为 kk ,那么 lowbit(x)=x&x=2klowbit(x) = x \& -x = 2^{k} 。观察上图可以发现: c[x]c[x] 在第 kk 层,例如 8(10)=1000(2)8_{(10)} = 1000_{(2)} 所以 c[8]c[8] 在第三层。

c[x]=a(xlowbit(x),x]c[x] = \sum a(x-lowbit(x), x]

  • lowbitlowbit 操作:

    int lowbit(int n) {
        return n & -n;
    }
    
  • xx 前缀和操作:
    a[1x]=c[x]+c[xlowbit(x)]+c[xlowbit(x)lowbit(xlowbit(x))]++c[1]\sum a[1 \dots x] = c[x] + c[x - lowbit(x)] + c[x - lowbit(x) - lowbit(x - lowbit(x))] + \dots + c[1]

    int query(int i) {
        int sum = 0;
        for (; i > 0; i -= lowbit(i)) sum += tr[i];
        return sum;
    }
    
  • 修改操作:
    从图中不难看出,假如修改 a[7]a[7] ,那么 c[7],c[8],c[16]c[7], c[8], c[16] 会被修改,也就是说,如果修改位置 xx 的数字,那么树状数组中 x,x+lowbit(x),x+lowbit(x)+lowbit(x+lowbit(x)),,nx, x + lowbit(x), x + lowbit(x) + lowbit(x + lowbit(x)), \dots, n 位置上的值会被更改。

    void add(int i, int x) {
        for (; i <= n; i += lowbit(i)) tr[i] += x;
    }	
    

初始化树状数组的操作就相当于把 xx 位置自增 a[x]a[x] ,直接使用修改操作增加即可。

思路

直接使用树状数组。

代码

import java.util.*;
import java.io.*;

public class Main {
    static int n, m;
    static int[] a, tr;
    
    static int lowbit(int n) {
        return n & -n;
    }
    
    static void add(int i, int x) {
        for (; i <= n; i += lowbit(i)) tr[i] += x;
    }
    
    static int query(int i) {
        int sum = 0;
        for (; i > 0; i -= lowbit(i)) sum += tr[i];
        return sum;
    }
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        n = (int) in.nval;
        in.nextToken();
        m = (int) in.nval;
        a = new int[n + 1];
        tr = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            a[i] = (int) in.nval;
        }
        for (int i = 1; i <= n; ++i) add(i, a[i]);
        while (m-- > 0) {
            in.nextToken();
            int k = (int) in.nval;
            in.nextToken();
            int x = (int) in.nval;
            in.nextToken();
            int y = (int) in.nval;
            if (k == 0) System.out.println(query(y) - query(x - 1));
            else add(x, y);
        }
    }
}

方法二:线段树

思路

线段树模板

代码

import java.util.*;
import java.io.*;

public class Main {
    static int n, m;
    static int[] a;
    static class Node {
        int l, r, sum;
        
        public Node(int l, int r, int sum) {
            this.l = l;
            this.r = r;
            this.sum = sum;
        }
    }
    static Node[] tr;
    
    static void pushup(int u) {
        tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    }
    
    static void build(int u, int l, int r) {
        if (l == r) tr[u] = new Node(l, r, a[r]);
        else {
            tr[u] = new Node(l, r, 0);
            int m = l + r >> 1;
            build(u << 1, l, m);
            build(u << 1 | 1, m + 1, r);
            pushup(u);
        }
    }
    
    static int query(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
        else {
            int m = tr[u].l + tr[u].r >> 1;
            int sum = 0;
            if (l <= m) sum += query(u << 1, l, r);
            if (r >= m + 1) sum += query(u << 1 | 1, l, r);
            return sum;
        }
    }
    
    static void modify(int u, int i, int x) {
        if (tr[u].l == tr[u].r) tr[u].sum += x;
        else {
            int m = tr[u].l + tr[u].r >> 1;
            if (i <= m) modify(u << 1, i, x);
            else modify(u << 1 | 1, i, x);
            pushup(u);
        }
    }
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        n = (int) in.nval;
        in.nextToken();
        m = (int) in.nval;
        a = new int[n + 1];
        tr = new Node[n * 4];
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            a[i] = (int) in.nval;
        }
        build(1, 1, n);
        while (m-- > 0) {
            in.nextToken();
            int k = (int) in.nval;
            in.nextToken();
            int x = (int) in.nval;
            in.nextToken();
            int y = (int) in.nval;
            if (k == 0) System.out.println(query(1, x, y));
            else modify(1, x, y);
        }
    }
}
0

评论区