打鼹鼠_二维树状数组(信息学奥赛一本通- P1540)(二维树状数组模版题)

【题目描述】

这是一道模板题。

给出一个 n×m 的零矩阵 A,你需要完成如下操作:

1xyk:表示元素 Ax,y自增 k;

2abcd:表示询问左上角为 (a,b),右下角为 (c,d) 的子矩阵内所有数的和。

【输入】

输入的第一行有两个正整数 n,m;

接下来若干行,每行一个操作,直到文件结束。

【输出】

对于每个 2 操作,输出一个整数,表示对于这个操作的回答。

【输入样例】

复制代码
2 2
1 1 1 3
1 2 2 4
2 1 1 2 2

【输出样例】

复制代码
7

【提示】

数据范围与提示:

对于 10% 的数据,n=1;

对于另 10% 的数据,m=1;

对于全部数据,1≤n,m≤2^12,1≤x,a,c≤n,1≤y,b,d≤m,∣k∣≤10^5 ,保证操作数目不超过 3×10^5 ,且询问的子矩阵存在。

一、 题目分析

【题目大意】 维护一个n×m的初始全为0的矩阵 A,要求高效支持两种操作:

  1. 单点修改:将矩阵中坐标为(x,y)的元素自增 k。

  2. 子矩阵求和:查询左上角为(a,b),右下角为 (c,d) 的子矩阵内所有元素的和。

【数据范围与核心矛盾】 1≤n,m≤4096,操作总数 ≤3×10^5。 如果每次修改是 O(1),查询用两层 for循环暴力遍历,查询的时间复杂度将高达O(N×M),在 30 万次操作下绝对会超时。我们必须将单次操作的复杂度降到对数级别,即O(logN×logM)。


二、 思考过程

  1. 一维到二维的推演 : 一维树状数组tree[x]维护的是一段长度为lowbit(x)的线状区间。因为二维平面的横纵坐标是绝对正交、互不干扰的,所以当我们升维到二维时,tree[x][y]维护的就是一个矩形面积 :宽度为lowbit(x),高度为lowbit(y)。

  2. 修改操作(单点波及面) : 当 (x,y) 发生改变时,我们需要更新所有"管辖领地"包含 (x,y) 的大节点。在一维中是用for循环不断 i+=lowbit(i) 往后跳;在二维中,只需要将x轴的跳跃和y轴的跳跃进行双层嵌套组合即可。

  3. 查询操作(前缀矩阵和) : 同样利用双层嵌套,x-=lowbit(x) 配合 y-=lowbit(y),我们可以极其高效地拼凑出从左上角 (1,1) 到右下角 (x,y) 的整个前缀矩阵的面积和。


三、 解题思路

树状数组原生的 query(x,y) 只能求出"紧贴左上角"的前缀矩阵和。但题目要求的是任意一个悬空的子矩阵 (a,b) 到 (c,d) 的和。

这时候必须引入二维前缀和的核心------容斥原理: 要计算悬空子矩阵的面积,我们可以用大矩形挖去多余的部分:

  1. 先拿到大矩形:query(c,d)。

  2. 挖掉它上方多余的矩形:-query(a-1,d)。

  3. 挖掉它左边多余的矩形:-query(c,b-1)。

  4. 因为左上角的矩形被挖了两次,必须补偿回来:+query(a-1,b-1)。

最终公式:Ans=query(c,d)-query(c,b-1)-query(a-1,d)+query(a-1,b-1)。


四、 算法设计

  • 数据结构 :二维数组c[5000][5000]。因为矩阵求和极易突破21亿的整型极限,该数组及查询函数的返回值必须使用long long。

  • lowbit函数 :x&(-x),提取二进制最低位的1。

  • add函数 :双层for循环,控制变量均使用+=lowbit() 向上攀升更新。

  • query 函数 :双层for循环,控制变量均使用-=lowbit() 向下聚拢求和。

  • IO 优化 :使用 ios::sync_with_stdio(false); cin.tie(0); 应对 30 万次的庞大输入流。


五、 时空复杂度分析

  • 时间复杂度:

    • 单次add和query的时间复杂度均为O(logN×logM)。

    • 总时间复杂度为O(Q×logN×logM),在N=4096时,logN≈12。单次操作最多只需循环144 次。30 万次操作的计算量在千万级别,1 秒内毫无压力。

  • 空间复杂度:需要开辟一个5000×5000的64位整数数组,空间复杂度O(N×M),约占用 200MB内存,符合常规竞赛的空间限制。


六、 易错点总结

  1. 容斥边界切勿漏减1 :在容斥原理公式中,必须是a-1和b-1。如果写成a和b,会把子矩阵边界上的那一行/那一列也给错误地挖掉。

  2. 全局变量与局部变量撞车 :我们以往的风格是将树状数组命名为 c。而在处理询问时,局部变量又定义了 int a,b,c,d;。虽然C++允许局部变量遮蔽全局变量,使得 query(c, d) 传递的是局部变量,但这在工程上是极度危险的做法。强烈建议将全局树状数组命名为cc,避免 Bug。

  3. 数据溢出 :二维求和极其庞大,一定要将ans和cc数组开成long long。


七、 题解

本题是二维树状数组最纯粹的模板题。通过两层嵌套循环将一维的线段管辖扩展至二维的矩形管辖,再辅以二维前缀和的容斥原理,用极其轻量级的代码和极小的常数,完美解决了二维动态矩阵的维护问题。相比于二维线段树(树套树),二维树状数组是考场上兼顾速度与代码稳定性的最优解。


八、 完整代码

cpp 复制代码
#include <iostream>
using namespace std;
long long cc[5000][5000];//树状数组
int n,m;

//返回x二进制表示下最低位1所代表的整数
int lowbit(int x){
    return x&(-x);
}

//树状数组更新操作
void add(int x,int y,int k){
    //双层循环,x轴和y轴均向上攀升,更新所有包含(x,y)的大管辖矩阵
    for(int i=x;i<=n;i+=lowbit(i)){
        for(int j=y;j<=m;j+=lowbit(j)){
            cc[i][j]+=k;
        }
    }
}
//树状数组查询操作
//查询左上角为(1,1),右下角为(x,y)的前缀矩阵和
long long query(int x,int y){
    long long ret=0ll;//记录前缀和
    //双层循环,x轴和y轴均向下聚拢,无缝拼凑出前缀面积
    for(int i=x;i>0;i-=lowbit(i)){
        for(int j=y;j>0;j-=lowbit(j)){
            ret+=cc[i][j];
        }
    }
    return ret;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    int t;
    while(cin>>t){
        if(t==1){//自增操作
            int x,y,k;
            cin>>x>>y>>k;
            add(x,y,k);
        }
        else{//查询操作
            int a,b,c,d;
            cin>>a>>b>>c>>d;
            //套用二维前缀和容斥原理公式
            //务必注意是a-1和b-1,否则会多挖掉一行一列
            cout<<query(c,d)-query(c,b-1)-query(a-1,d)+query(a-1,b-1)<<"\n";
        }
    }
}
相关推荐
民乐团扒谱机12 小时前
【微实验】NSGA-II(非支配排序遗传算法)——解决多目标优化问题的“自然选择模拟器”
linux·人工智能·算法
青少儿编程课堂13 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:中缀表达式求值解析
c++·python·算法·bfs·信息学竞赛
武子康15 小时前
Qwen-Drive 到底是什么:4B 级 VLM 如何成为驾驶策略底座
人工智能·算法·机器学习
zh_xuan16 小时前
LeeCode 496. 下一个更大元素I
数据结构·算法·栈·单调栈
机 _ 长16 小时前
05 智算运维|8 张卡怎么才算「捆成一张」:NCCL 实测与拓扑
运维·人工智能·算法·智算运维
早起CaiCai17 小时前
【2023-01 OM】利用深度学习算法融合异质观测实现海洋场降尺度
人工智能·深度学习·算法
梦里寻码18 小时前
我用 NestJS + Vue3 写了一套完整的研发管理系统(58 表 / 229 API / 130 项断言,全开源
前端·后端·算法
猿人谷18 小时前
Surrogate Model:让进化算法学会“预测”,为什么它正在成为智能优化的重要技术?
后端·算法·架构
青少儿编程课堂20 小时前
蓝桥杯青少组图形化 STEMA 省考(报名倒计时·11/22开考)+ 全国青少年信息素养大赛 2026—2027赛季 + 电子学会图形化考级:校园华容道大闯关
c++·python·算法·bfs·信息学竞赛