#P1139. 第3题-小美的收藏夹
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 581
            Accepted: 164
            Difficulty: 6
            
          
          
          
                       所属公司 : 
                              美团
                                
            
                        
              时间 :2023年4月1日
                              
                      
          
 
- 
                        算法标签>树状数组          
 
第3题-小美的收藏夹
题目思路
思路:树状数组模板题
转化题意之后,问题变为:给定一个序列,需要维护两类操作:单点修改值 + 区间查询和 . 这是经典的树状数组(BIT)的应用,简称裸题/模板题。我们可以做到O(log n) 的修改和查询。
没有接触过的同学,塔子哥在此推荐以下几个学习网站:
推荐理由:manim制作,生动形象。UP主是鹤翔万里,OIer佬。现在浙大cs读本科。质量非常高!
推荐理由:文章质量非常高。有严谨的证明+详尽的代码+例题。此外,其他很多算法上面也有写。刷题必备。
题外话
当时这道题在赛码上是放暴力算法过了。也就是你不用上树状数组,直接模拟就能在笔试中拿满分。但是塔子哥并不推荐去赌出题人会手下留情。
类似题目推荐
leetcode
CodeFun2000
P1217 华为秋招-2022年10月12日-第二题-幼儿园排队报数
P1053 华东师范大学保研机试-2022-Minimum_Sum
P1131 腾讯春招-2023.03.26-第四题-顺子区间
P1263 塔子月赛1-第二题-2333的超级队列 (树状数组上二分,难度较高!)
代码
C++
#include<bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
// 树状数组模板
#define lb(x) ((x) & (-x))
int a[N], n, op[N], x[N], y[N];
int get_pre(int i) {
    int ret = 0;
    for (; i > 0;i -= lb(i)) ret += a[i];
    return ret;
}
void modify(int i, int val) {
    for (; i <= n; i += lb(i)) a[i] += val;
}
int get(int l, int r) {
    if (l > r) return 0;
    return get_pre(r) - get_pre(l - 1);
}
// end
int main() {
    int m;
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        cin >> op[i];
    }
    for (int i = 1; i <= m; ++i) {
        cin >> x[i];
    }
    for (int i = 1; i <= m; ++i) {
        cin >> y[i];
    }
    // 模板,没什么好讲的
    for (int i = 1; i <= m; ++i) {
        if (op[i] == 1) {
            cout << get(x[i], y[i]);
            cout << " ";
        } else {
            int p = get(x[i] , x[i]);
            modify(x[i], -p);
            modify(x[i], y[i]);
        }
    }
    return 0;
}
python
# 树状数组类
class NumArray:
    def __init__(self, nums):
        self.nums = nums
        self.tree = [0] * (len(nums) + 1)
        for i, num in enumerate(nums, 1):
            self.add(i, num)
    # 向树状数组中添加元素
    def add(self, idx, val):
        while idx < len(self.tree):
            self.tree[idx] += val
            idx += (idx & -idx)
    # 计算前缀和
    def preSum(self, idx):
        s = 0
        while idx:
            s += self.tree[idx]
            idx -= (idx & -idx)
        return s
    # 更新nums中的元素,并同步更新树状数组
    def update(self, index: int, val: int) -> None:
        self.add(index + 1, val - self.nums[index]) # 将更新差值加入树状数组中
        self.nums[index] = val # 更新nums中的元素
    # 计算区间和
    def sumRange(self, left: int, right: int) -> int:
        return self.preSum(right+1) - self.preSum(left)
n, m = map(int, input().split())
op = [[0] * m for _ in range(3)]
for i in range(3):
    op[i] = list(map(int, input().split()))
obj = NumArray([0] * n)
for i in range(m):
    if op[0][i] == 0: # 更新操作
        obj.update(op[1][i]-1, op[2][i])
    else: # 区间查询操作
        print(obj.sumRange(op[1][i]-1, op[2][i]-1), end=" ")
Java
import java.util.Scanner;
public class Main {
    static int[] tree; // 树状数组
    static int[] item; // 数组元素
    static int n; // 数组长度
    // 计算x的最后一位1所代表的数值,就是lowbit(x)
    static int lowbit(int x){
        return x & -x;
    }
    // 在树状数组的第x个位置增加u
    static void add(int x,int u){
        for (int i = x; i <= n; i+=lowbit(i)) {
            tree[i]+=u;
        }
    }
    // 查询前缀和,返回[1,x]上所有元素之和
    static int query(int x){
        int res = 0;
        for (int i = x; i >0; i-=lowbit(i)) {
            res+=tree[i];
        }
        return res;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        int m = sc.nextInt();
        int[] op = new int[m]; // 操作代码
        int[] x = new int[m]; // x参数
        int[] y = new int[m]; // y参数
        for (int i = 0; i < m; i++) {
            op[i] = sc.nextInt();
        }
        for (int i = 0; i < m; i++) {
            x[i] = sc.nextInt();
        }
        for (int i = 0; i < m; i++) {
            y[i] = sc.nextInt();
        }
        item = new int[n+1]; // 数组元素
        tree = new int[n+1]; // 树状数组
        for (int i = 1; i <= n; i++) {
            add(i,item[i]); // 初始化树状数组,将item中的元素加入其中
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < m; i++) {
            if(op[i]==0){ // 更新操作
                add(x[i],y[i]-item[x[i]]); // 将tree[x]增加y[i]-item[x[i]],相当于更新差值
                item[x[i]] = y[i]; // 更新item[x]
            }else { // 区间查询操作
                sb.append(query(y[i])-query(x[i]-1)+ " ");
            }
        }
        System.out.println(sb.toString());
    }
}
Go
package main
import (
    "bufio"
    "fmt"
    "os"
)
var (
    n     int     // 数组长度
    tree  []int   // 树状数组
    item  []int   // 数组元素
    op    []int   // 操作代码
    x     []int   // x参数
    y     []int   // y参数
)
// 计算x的最后一位1所代表的数值,就是lowbit(x)
func lowbit(x int) int {
    return x & -x
}
// 在树状数组的第x个位置增加u
func add(x, u int) {
    for i := x; i <= n; i += lowbit(i) {
        tree[i] += u
    }
}
// 查询前缀和,返回[1,x]上所有元素之和
func query(x int) int {
    res := 0
    for i := x; i > 0; i -= lowbit(i) {
        res += tree[i]
    }
    return res
}
func main() {
    reader := bufio.NewReader(os.Stdin)
    fmt.Fscan(reader, &n)
    var m int
    fmt.Fscan(reader, &m)
    op = make([]int, m)
    x = make([]int, m)
    y = make([]int, m)
    for i := 0; i < m; i++ {
        fmt.Fscan(reader, &op[i])
    }
    for i := 0; i < m; i++ {
        fmt.Fscan(reader, &x[i])
    }
    for i := 0; i < m; i++ {
        fmt.Fscan(reader, &y[i])
    }
    item = make([]int, n+1)
    tree = make([]int, n+1)
    for i := 1; i <= n; i++ {
        add(i, item[i]) // 初始化树状数组,将item中的元素加入其中
    }
    var result string
    for i := 0; i < m; i++ {
        if op[i] == 0 { // 更新操作
            add(x[i], y[i]-item[x[i]]) // 将tree[x]增加y[i]-item[x[i]],相当于更新差值
            item[x[i]] = y[i]           // 更新item[x]
        } else { // 区间查询操作
            result += fmt.Sprintf("%d ", query(y[i])-query(x[i]-1))
        }
    }
    fmt.Print(result)
}
Js
let input = '';
process.stdin.resume();
process.stdin.setEncoding('utf-8');
process.stdin.on('data', (data) => {
    input += data;
});
process.stdin.on('end', () => {
    const lines = input.trim().split('\n');
    const n = parseInt(lines[0].trim().split(' ')[0]);
    const m = parseInt(lines[0].trim().split(' ')[1]);
    let op = lines[1].trim().split(' ').map(Number);
    let x = lines[2].trim().split(' ').map(Number);
    let y = lines[3].trim().split(' ').map(Number);
    
    // 接下来是树状数组的模板
    let item = new Array(n+1).fill(0);
    let tree = new Array(n+1).fill(0);
    function lowbit(x){
        return x & -x;
    }
    
    function add(x,u){
        for(let i=x;i<=n;i+=lowbit(i)){
            tree[i] += u;
        }
    }
    
    function query(x){
        let res = 0;
        for(let i=x; i>0;i-=lowbit(i)){
            res += tree[i];
        }
        return res;
    }
	// end
    
    
    let sb = '';
    for(let i=0;i<m;i++){
        if(op[i]==0){ // 更新操作
            add(x[i],y[i]-item[x[i]]); // 将tree[x]增加y[i]-item[x[i]],相当于更新差值
            item[x[i]] = y[i]; // 更新item[x]
        }else { // 区间查询操作
            sb += `${query(y[i])-query(x[i]-1)} `;
        }
    }
    console.log(sb.trim());
});
        题目内容
小美是一个热爱收藏的年轻人,他喜欢收集各种有趣的物品,例如邮票、硬币、瓶盖等等。他的收藏品越来越多,于是他决定为自己在收藏架上建了一排 n 个收藏夹,分别编号为 1,2,3…n。这样,他就可以更好地组织和展示自己的收藏品了。
小美有些时候会突发奇想改变某一个收藏美里的内容,例如从中拿入、拿出一些藏品,这些的操作会改变小美对这个收藏夹的欣赏程度,我们记编号为 i 的收藏夹,小美对其的欣赏程度为 ai 。小美在休息时间经常会欣赏连续编号的收藏夹,例如编号为 L,L+1,L+2,...,R−1,R 的这些收藏夹,他能从中获得的满足感为这些收藏失的欣赏程度之和,即 ∑i=LRai 。
小美想在欣赏之前提前估算自己能得到的满足感,想知道如果他选择编号区间为 [L,R] 的收藏夹,能给他带来的满足感是多少。但是小美不想自己计算,所以他想你帮他计算一下,然后告诉他。
输入描述
第一行两个整数 n 和 m ,表示小美的收藏夹数量和小美的操作数量。初始时刻收藏夹都是空的,也即 ai=0 ( i∈[1,n] )
第二行 m 个整数 op1,op2,…,opm 。
第三行 m 个整数 x1,x2,…,xm 。
第四行 m 个整数 y1,y2,…,ym ,这些共同表示了 m 次操作,对于第 i 次操作, opi=0 时表示为一次收藏夹更新操作,会将 xi 位置的收藏夹欣赏程度更新为 yi ,即 axi=yi ; opi=1 时表示为一次查询操作,表示如果小美欣赏编号在区间 [xi,yi] 的收藏夹,能获得的满足感是多少,也即 ∑j=xiyiaj 。
对于所有的数据, 1≤n,m≤50000,opi∈[0,1] ,当 opi=0 时, 1≤xi≤n,0≤yi≤10000 ;当 opi=1 时, 1≤xi≤yi≤n ,保证至少有一次 opi=1 的操作。
输出描述
对每一个 opi=1 的操作,输出一个数表示对应答案。空格隔开所有答案。
样例
输入
4 7
1 0 1 0 1 0 1
1 1 1 3 1 4 1
3 2 3 5 3 100 3
输出
0 2 7 7
样例解释 操作记录为
0 0 0 0(初始)
询问[1,3]结果为0+0+0>
2 0 0 0<1号更改为2>
<询问[1,3],结果为2+0+0>
2 0 5 0 <3号更改为5>
<询问[1,3]结果为2+0+5>
2 0 5 100<4号更改为100>
<询问[1,3],结果为2+0+5>