AcWing 798 差分矩阵

题目描述:

输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2, c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。

每个操作都要将选中的子矩阵中的每个元素的值加上c。

请你将进行完所有操作后的矩阵输出。

输入格式

第一行包含整数n,m,q。

接下来n行,每行包含m个整数,表示整数矩阵。

接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。

输出格式

共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。

数据范围

1≤n,m≤1000,
1≤q≤100000,
1≤x1≤x2≤n,
1≤y1≤y2≤m,
−1000≤c≤1000,
−1000≤矩阵内元素的值≤1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2

 分析:

本题考察二维差分,差分的具体概念见AcWing 100 IncDec序列。鉴于前缀和以及一维差分较为简单,不再单独写题解,简单的总结下:

一维前缀和:设有数组a[n],s[n]为a的前缀和数组,有s[i] = a[1] + a[2] +... + a[i],有了前缀和,我们可以在O(1)的时间内求出任意一段区间内a数组的和,比如a[l] + a[l + 1] + ... +a[r] = s[r] - s[l - 1].

二维前缀和:设有二维数组a[m][n],s[i][j]为a[i][j]及其之前所有元素的前缀和,比如下图中,从a[1][1]到a[i][j]矩形所有元素的和为s[i][j],s[i - 1][j]表示直线b上边所有元素的和,s[i][j - 1]表示直线a左边所有元素之和,则s[i][j] = s[i - 1][j] +s[i][j - 1] - s[i - 1][j -1] + a[i][j],因为a的左边与b的上边元素求和时对该区域内元素求了两遍和,所以需要减去s[i - 1][j - 1].

既然知道了求二维数组的前缀和,那么求以(x1,y1)为矩形左上角,(x2,y2)为矩形右上角的矩形内所有元素的和可以用公式s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]来计算。如下图所示,(x2,y2)的前缀和减去1左边的元素之和(s[x1 - 1][y2])再减去2上边的元素之和(s[x2][y1 - 1])再加上多减去的3区域(s[x1 - 1][y1 - 1]。

一维差分:差分是与前缀和相对的概念,设有数组a[n],b[n],b[n]的前缀和数组为a[n],则b[n]为a[n]的差分数组,即b[1] = a[1],b[2] = a[2] - a[1],b[3] = a[3] - a[2],...,b[n] = a[n] - a[n - 1].可见把a数组相邻两个元素相见即可得到b数组,对b数组求和即可得到a,a[n] = a[1] + a[2] - a[1] + a[3] - a[2] +... + a[n] - a[n - 1] = b[1] + b[2] + ... + b[n]。差分的重要性质就是已知a数组可以求出b数组,已知b数组也可以反推出a数组。一维前缀和使得我们在O(1)的时间内求出区间l到r范围内元素的和,但是一旦涉及区间更新前缀和数组便没那么简单了,比如对下标1 - 3范围内的元素都加上2,2 - 4范围内的元素都加上1,每次进行区间更新,区间中a的每个元素都进行了更新,前缀和自然也一直在变。假设有m次区间更新操作,每次对长度为n的区间范围内的元素都加上一个数c,一共需要进行m * n次操作才能得到最终的a数组。差分则是简化此操作的利器,比如数组a,下标1 - 10的元素分别为1 2 3 4 5 6 7 8 9 2,对下标为2 - 9的元素都加上3需要进行8次操作,而该操作对a的差分数组的影响仅仅是b[2] += 3,b[10] -= 3。对区间的操作简化为了对区间端点的操作,不管区间更新多长,对b数组的影响始终就两个元素,m次更新结束,对b数组一共进行了2m次操作,再利用b求出a问题即可解决。总之,给数组a[l,r]范围内的数都加上c的操作等价于b[l] += c,b[r + 1] -= c的操作。

二维差分:经过了这么多的铺垫,终于讲到了本题的主题二维差分。设有二维数组a[m][n]及它的差分数组b[m][n],我们唯一知道的一个性质就是b的前缀和是a,由二维前缀和的一个公式s[i][j] = s[i - 1][j] +s[i][j - 1] - s[i - 1][j -1] + a[i][j]可得,a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j],变形得b[i][j] = a[i][j] + a[i - 1][j - 1] - a[i - 1][j] - a[i][j - 1]。等式的右边可以理解为一个长度为1的单位矩阵,b[i][j]的值就等于以a[i][j]为右下顶点的正方形的正对角线上两个端点的和减去副对角线上两个端点的和。上面的式子对我们理解二维差分尤为重要(但是为啥很少见讲二维差分的文章提及),我们可以得出一个结论,对于二维数组a,如果a[i][j],a[i - 1][j - 1],a[i - 1][j],a[i][j - 1]都加上c,b[i][j]不变,如果只有一条边上的两个端点加上c,比如a[i][j]和a[i][j - 1],b[i][j]的值也不变。那么对于下图的矩阵,我们对以(x1,y1)为左上顶点,(x2,y2)为右下顶点的矩形区域内元素都加上c,思考下会改变哪些b数组的元素,显而易见,矩形内部的点比如a[x][y](x1<x<x2,y1<y<y2),以该点为右下顶点的边长为1的正方形区域的四个顶点元素都是加上c的,根据上面的结论b[x][y]不会改变,那么对于像下图右边界上两条黄线构成的正方形呢,由于只有一条黑色边上两个顶点的值加上了c,所以该正方形右下端点的b数组的值也不会改变。经过分析,对矩形区域内所有的元素都加上c后,b数组受影响的只有四个点,分别是b[x1][y1] ,b[x2 + 1][y1],b[x1][y2 + 1] , b[x2 + 1][y2 + 1] ;以这些点为右下端点的正方形也就是下图中红色的四个正方形,由于只有一个端点的值被改变,所以他们的b值均被改变,具体变化为b[x1][y1] += c; b[x2 + 1][y1] -= c;b[x1][y2 + 1] -= c;b[x2 + 1][y2 + 1] += c;于是我们得出的结论是对该矩形区域内所有元素都加上c的结果等价于对b中四个点的操作。

 

总的代码如下:

#include <iostream>
using namespace std;
const int maxn = 1005;
int a[maxn][maxn],b[maxn][maxn];
void insert(int x1,int y1,int x2,int y2,int c){
    b[x1][y1] += c;
    b[x2 + 1][y1] -= c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y2 + 1] += c;
}
int main(){
    int n,m,q,x1,y1,x2,y2,c;
    scanf("%d%d%d",&n,&m,&q);
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            scanf("%d",&a[i][j]);
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            insert(i,j,i,j,a[i][j]);
    while(q--){
        scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&c);
        insert(x1,y1,x2,y2,c);
    }
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= m;j++){
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

我们在初始化二维差分数组时,可以直接用上面推导的求b的公式,一般情况下则是直接将a中原有的元素逐个插入,以此来影响b,这样则不需要要考虑由a推出b的公式了。 

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
二维分是一种常用的数据结构和算法巧,用于高效地处理二维矩阵区间的更新和查询操作。它可以在O(1)的时间复杂度内完成区间的更新和查询操作,相比传统的暴力遍历方法,具有更高的效率。 二维分的基本思想是将原始矩阵转化为一个矩阵矩阵中的每个元素表示原始矩阵中相邻元素之间的值。通过对矩阵进行预处理,可以实现对原始矩阵区间的更新和查询操作。 具体来说,二维分的操作包括两个步骤:预处理和操作。预处理阶段,需要根据原始矩阵构建矩阵;操作阶段,可以通过对矩阵的更新来实现对原始矩阵区间的更新,同时可以通过对矩阵的求和来实现对原始矩阵区间的查询。 下面是二维分的基本操作: 1. 构建矩阵:对于原始矩阵A,构建一个矩阵B,其中B[i][j] = A[i][j] - A[i-1][j] - A[i][j-1] + A[i-1][j-1]。 2. 区间更新:对于原始矩阵A的一个区间[left, right] x [top, bottom],将矩阵B的相应位置进行更新,即B[left][top] += val,B[right+1][top] -= val,B[left][bottom+1] -= val,B[right+1][bottom+1] += val。 3. 区间查询:对于原始矩阵A的一个区间[left, right] x [top, bottom],通过求和矩阵B的相应位置得到区间和,即sum = B[right][bottom] - B[left-1][bottom] - B[right][top-1] + B[left-1][top-1]。 二维分可以广泛应用于各种算法问题,例如矩阵区间求和、矩阵区间更新等。它的时间复杂度较低,适用于处理大规模的数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值