二维前缀和和二维差分

二维前缀和

https://codeforces.com/contest/1581/problem/C
题目意思是问在给定的 01 01 01矩阵中,横向长度至少为 5 5 5,纵向长度至少为 4 4 4,把这样一个矩形的四周除了四个顶点其他部分都变成 1 1 1,矩形内部全都变成 0 0 0,问最少需要多少次操作,每次操作可以把一个位置的 1 1 1变成 0 0 0,或者把一个位置的 0 0 0变成 1 1 1

  • 题目意思读懂以后,我们应该能够形成一个思路,就是找到所有的这样符合条件的矩形,看需要进行多少次操作,取其中最小的操作数即可,一个矩形需要进行的操作数量应该是他内部矩形的 1 1 1的数量加上边上除了顶点之外的 0 0 0的数量,如何高效操作呢?这里简单介绍一下二维前缀和
    在这里插入图片描述
  • 我们使用一个数组 f [ i ] [ j ] f[i][j] f[i][j]存储二维前缀和,表示以 ( 1 , 1 ) (1,1) (1,1)为左上角顶点,以 ( i , j ) (i,j) (i,j)为右下角顶点,的矩形内部所有元素的和(包括边缘),如果我们想求出如上图,以 ( a , b ) (a,b) (a,b)为左上角顶点,以 ( c , d ) (c,d) (c,d)为右下角顶点的矩形内部所有元素之和,怎么求?很简单, f [ c ] [ d ] − f [ c ] [ b − 1 ] − f [ a − 1 ] [ d ] + f [ a − 1 ] [ b − 1 ] f[c][d]-f[c][b-1]-f[a-1][d]+f[a-1][b-1] f[c][d]f[c][b1]f[a1][d]+f[a1][b1]即可,因为左上角的矩形被减掉了两次
  • 那么我们怎么求 f [ i ] [ j ] f[i][j] f[i][j]呢?,根据左、右、左上三个位置的 f f f值即可求出,也就是 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − 1 ] − f [ i − 1 ] [ j − 1 ] + f [ i ] [ j ] f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+f[i][j] f[i][j]=f[i1][j]+f[i][j1]f[i1][j1]+f[i][j],这里同样左上角被加了两次,所以需要减去一个
    明白二维前缀和之后我们再来看这道题就显得很简单了,我们只需要枚举每个矩形,查看所有情况就可以了,注意顶点上的 1 1 1不能算在答案里
  • 但是这样看时间复杂度似乎是 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)的,对于 400 400 400的数据范围看起来好像不行,这道题目似乎有点离谱,好像 c f cf cf里面做这种剪枝的不太多,极端情况应该是过不了,但是有一种剪枝方法,我们扩展矩形的方式是固定左上角,根据右下角顶点往右下扩展,先右后下,那么如果当前矩形左边,上下两条边及矩形内部的操作次数已经大于当前答案,那么继续往右也不会得到更优的答案,所以这里 b r e a k break break掉,加这个小小的剪枝可以通过此题
#include <bits/stdc++.h>

using namespace std;
const int MAXN = 500;
char mp[MAXN][MAXN];
int f[MAXN][MAXN];
int cal(int a, int b, int c, int d){
    return f[a][b] + f[c - 1][d - 1] - f[a][d - 1] - f[c - 1][b];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t, n, m;
    cin >> t;
    while(t--){
        cin >> n >> m;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin >> mp[i][j];
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + mp[i][j] - '0';
            }
        }
        int ans = 20;
        for(int x=1;x<=n;x++){
            for(int y=1;y<=m;y++){
                for(int i=x+4;i<=n;i++){
                    for(int j=y+3;j<=m;j++){
                        // cout << x << ' ' << y << ' ' << i << ' ' << j << '\n';
                        int jia_l = mp[x][y] + mp[i][y] - '0' - '0';
                        int jia_r = mp[i][j] + mp[x][j] - '0' - '0';
                        int wai = cal(i, j, x, y);
                        int nei = cal(i - 1, j - 1, x + 1, y + 1);
                        int len = i - x - 1;
                        int width = j - y - 1;
                        int r = cal(i, j, x, j) - jia_r;
                        int num = nei + 2 * width + len - (wai - nei - jia_l - jia_r - r);
                        if(ans < num) break;
                        ans = min(ans, nei + 2 * (len + width) - (wai - nei - jia_l - jia_r));
                        // cout << wai << ' ' << nei << ' ' << jia_l << ' ' << jia_r << '\n';
                    }
                }
            }
        }
        cout << ans << '\n';
    }
    return 0;
}

上几道二维前缀和的简单例题
https://www.luogu.com.cn/problem/P1719

#include <bits/stdc++.h>

using namespace std;
const int MAXN = 150;
int f[MAXN][MAXN];
int cal(int a, int b, int c, int d){
    return f[c][d] - f[c][b - 1] - f[a - 1][d] + f[a - 1][b - 1];
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin >> f[i][j];
            f[i][j] += f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1];
        }
    }
    int ans = INT_MIN;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=i;k++){
                for(int q=1;q<=j;q++){
                    ans = max(ans, cal(k, q, i, j));
                }
            }
        }
    }
    cout << ans;
    return 0;
}

http://47.110.135.197/problem.php?id=5182
http://47.110.135.197/problem.php?id=5183

二维差分

  • 最简单的形式就是给你一个矩形区域,把所有点位上的值+1,最后问每个位置的值是多少,直接做显然是 O ( n m q ) O(nmq) O(nmq)的,但可以用二维差分优化到 O ( q + n m ) O(q+nm) O(q+nm)
  • 有了一维差分和二维前缀和的基础,我们现在考虑二维前缀和维护的过程中如何消掉影响,一维前缀和是在最后一个位置的后一个减1,那么二维差分可以推理得到应该是在第一行的最后一个位置的后一个减1,同时在第一列的最后一个位置的最后一个位置的后一个减1,如下图,阴影部分表示要求修改的区域
    在这里插入图片描述
  • 所以设二维数组是 e [ i ] [ j ] e[i][j] e[i][j],操作是 e [ a ] [ b ] + 1 , e [ a ] [ d + 1 ] − 1 , e [ c + 1 ] [ b ] − 1 , e [ c ] [ d ] + 1 e[a][b]+1,e[a][d+1]-1,e[c+1][b]-1,e[c][d]+1 e[a][b]+1,e[a][d+1]1,e[c+1][b]1,e[c][d]+1
  • 计算差分数组的时候按照二维前缀和的思路,从左上往右下更新,更新操作是 e [ i ] [ j ] = e [ i ] [ j ] + e [ i ] [ j − 1 ] + e [ i − 1 ] [ j ] − e [ i − 1 ] [ j − 1 ] e[i][j]=e[i][j]+e[i][j-1]+e[i-1][j]-e[i-1][j-1] e[i][j]=e[i][j]+e[i][j1]+e[i1][j]e[i1][j1]

例题
https://www.luogu.com.cn/problem/P5542

  • 注意面积和点的区别
#include <bits/stdc++.h>

using namespace std;

int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, k;
  cin >> n >> k;
  vector<vector<int> > e(1000 + 1, vector<int>(1000 + 1));
  for(int i=0;i<n;i++){
    int x1, x2, y1, y2;
    cin >> x1 >> y1 >> x2 >> y2;
    x1 += 1;
    y1 += 1;
    x2 += 1;
    y2 += 1;
    e[x1][y1] += 1;
    e[x2][y2] += 1;
    e[x1][y2] -= 1;
    e[x2][y1] -= 1;
  }
  int ans = 0;
  for(int i=1;i<=1000;i++){
    for(int j=1;j<=1000;j++){
      e[i][j] = e[i][j] + e[i - 1][j] + e[i][j - 1] - e[i - 1][j - 1];
      ans += e[i][j] == k;
    }
  }
  cout << ans << '\n';
  return 0;
}
### 三维前缀和差分算法概述 #### 一、二维前缀和的定义及计算方法 二维前缀和是一种用于快速求解子矩阵元素之的技术。对于给定的二维数组 `mat`,其对应的二维前缀和数组 `prefixSum` 的定义如下: 设原始矩阵大小为 \( m \times n \),则有: \[ \text{prefixSum}[i][j] = \sum_{p=0}^{i}\sum_{q=0}^{j} mat[p][q] \] 即,\( prefixSum[i][j] \) 表示从左上角 (0, 0) 到右下角 (i, j) 子矩阵中所有元素的总。 为了高效地计算该值,可以利用动态规划的思想递推得到: \[ \text{prefixSum}[i][j] = \text{prefixSum}[i-1][j] + \text{prefixSum}[i][j-1] - \text{prefixSum}[i-1][j-1] + mat[i][j] \] 其中需要注意边界条件:当索引越界时,默认对应项为零[^2]。 #### 二、二维差分的定义及计算方法 二维差分是对二维前缀和的一种逆操作,主要用于对某些区域内的数值进行批量修改后再查询整体变化情况。通过构造一个辅助数组 diff 来记录这些差异信息即可实现这一目标。 具体来说,在更新矩形范围(x1,y1)-(x2,y2)中的每一个单元格增加value的操作可以通过下面四次单独点上的增减完成整个过程而无需遍历内部节点逐一调整: 1. 增加 value 至位置 `(x1, y1)`; 2. 减少 value 自位置 `(x1, y2+1)` 起始处继续向后传播影响; 3. 同样减少 value 开始于`(x2+1, y1)` 并沿垂直方向向下延续效果; 4. 最终恢复多余扣除的部分于最远端角落 `(x2+1, y2+1)` 加回相同数量级补偿之前两次削减造成的影响。 最终实际应用阶段只需基于上述改造后的 diff 数组重新构建一次完整的累积版本就能还原出经过一系列区间变动之后的新状态下的初始输入表格形式[^1]。 ```python def apply_diff(mat, x1, y1, x2, y2, val): rows, cols = len(mat), len(mat[0]) # 创建并初始化差分数组 diff = [[0]*cols for _ in range(rows)] # 更新差分数组 diff[x1][y1] += val if x2+1 < rows: diff[x2+1][y1] -= val if y2+1 < cols: diff[x1][y2+1] -= val if x2+1 < rows and y2+1 < cols: diff[x2+1][y2+1] += val # 构造新的矩阵 result = [row[:] for row in mat] for i in range(rows): for j in range(cols): if i > 0: diff[i][j] += diff[i-1][j] if j > 0: diff[i][j] += diff[i][j-1] if i > 0 and j > 0: diff[i][j] -= diff[i-1][j-1] result[i][j] += diff[i][j] return result ``` #### 三、应用场景分析 1. **图像处理领域**: 在计算机视觉或者图形学当中经常涉及到像素级别的运算比如模糊滤镜等效果都可以借助于此技术加速局部区域内重复性较高的统计类任务执行效率。 2. **游戏地图生成器**: 对大规模地形编辑工具而言允许玩家即时预览自己绘制出来的图案是否满足特定规则约束条件下非常实用因为不需要每次改动都全盘扫描一遍而是仅仅关注发生变化的小块区域从而极大提升了用户体验流畅度[^3]. 3. **大数据分析平台**: 当面对海量多维时间序列数据集需要频繁截取不同时间段片段做进一步挖掘研究工作时候采用此类优化手段能够显著降低内存占用量同时也加快检索速度.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值