二维前缀和和二维差分

二维前缀和

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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Clarence Liu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值