题目描述
在 n×n 的格子上有 m 个地毯。
给出这些地毯的信息,问每个点被多少个地毯覆盖。
输入格式
第一行,两个正整数 n,m。意义如题所述。
接下来 m 行,每行两个坐标 (x1,y1)(x1,y1) 和 (x2,y2)(x2,y2),代表一块地毯,左上角是 (x1,y1)(x1,y1),右下角是 (x2,y2)(x2,y2)。
输出格式
输出 n 行,每行 n 个正整数。
第 i 行第 j 列的正整数表示 (i,j) 这个格子被多少个地毯覆盖。
输入输出样例
输入 #1
5 3 2 2 3 3 3 3 5 5 1 2 1 4输出 #1
0 1 1 1 0 0 1 1 0 0 0 1 2 1 1 0 0 1 1 1 0 0 1 1 1说明/提示
样例解释
覆盖第一个地毯后:
0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 覆盖第一、二个地毯后:
0 0 0 0 0 0 1 1 0 0 0 1 2 1 1 0 0 1 1 1 0 0 1 1 1 覆盖所有地毯后:
0 1 1 1 0 0 1 1 0 0 0 1 2 1 1 0 0 1 1 1 0 0 1 1 1 数据范围
对于20% 的数据,有 n≤50,m≤100。
对于100% 的数据,有 n,m≤1000。
解题思路
最暴力的解法就是直接遍历m个范围,来插入数据,那样的话通过java来实现这个算法一定会超时,时间复杂度直逼O(m*n*k),效率太低,可能C++,C或者是python可以通过暴力直接通过。所以我们要引入另一个思路,就是差分矩阵来优化算法,在情况符合的时候,二维的差分矩阵可以实现O(1)程度的优化.
前缀和的简单介绍
首先引入前缀和的概念,定义一个数组b[i],那么前缀和数组a[i]=b[1]+b[2]+....b[i],这就是前缀和的概念所以当我们有了前缀和数组,就可以求出原数组,即b[i] = a[i]-a[i-1]
而二维前缀和同理就是在一个二维的矩阵上,a[i][j]前缀和数组等于其前面所有b[i][j]之和,所以我们也可以在二维矩阵中根绝前缀和矩阵来获得原矩阵。这里关于前缀和的概念简单描述,如有不懂,可以参考前缀和与差分 图文并茂 超详细整理(全网最通俗易懂)
一维差分数组概念
前面我们了解了一维和二维的前缀和的概念,这里就可以引出差分的概念,假设a[i]是一个一维前缀和数组,那么我们通过前面的公式求出的原数组b[i]就称为a[i]的差分数组,而通过差分数组我们如何来优化我们的算法呢?我们可思考,假如在一段l-r的范围内,为这个范围内的数据加上一个数据,那么在原数组操作时我们需要经行(r-l)次循环来进行计算,假如我们把原数组看作一个前缀和数组,我们来构建他的差分数组,那么我们在一个差分数组的l项加1,那么在l项开始到结束所有的前缀和数组从l项开始都增加了1,前缀和数组a[l]=b[1]+b[2]+b[3]+...+b[l],我们给差分数组的b[l]+1,则a[i]=a[i]+1,由此我们也可以推断出a[i+1]也等于a[i+1],同理a[i+1]....一直到结束的所有项都等于原数据加一,但我们要实现的是l-r的数据修改,则我们就需要在差分数组的r+1项剪掉多余的部分,即b[r+1]-=1即可,这样我们便可以实现修改一项等于修改所有项,而我们只需要通过O(n)的时间复杂度构造差分数组即可,然后再通过O(n)的时间复杂的还原前缀和数组。
二位差分矩阵概念
我们可以从一维差分矩阵的概念推导出二位差分矩阵的概念,同理假设a[i][j]是一个一维前缀和数组,那么我们通过前面的公式求出的原数组b[i][j]就称为a[i][j]的差分数组,而唯一的不同就是在差分矩阵的构建方法上,我们假设差分矩阵已经构建好,那我们如何在差分矩阵上的x1,y1到x2,y2的子矩阵中所有元素增加一个数据呢,首先我们会考虑给b[x1][y1]加一,然后我们减去多余的部分,我们考虑一下这张图
当我们给b[x1][y1]+1,则从此点开始向右下发散的所有范围的前缀和数组都会加一,则我们需要减去图中的紫色和绿色的部分,但这两个部分会有一部分的重叠,就是图中的红色的部分,这就意味着红色的部分被剪掉了两次,则我们就需要加上红色的部分一次 ,这样我们就实现了一个从x1,y1到x2,y2的数据修改,其公式为
b[x1][y1]+=c
b[x1][y2+1]-=c
b[x2+1][y1]-=c
b[x2+1][y2+1]+=c
则我们将其写为代码为
public static void insertNum(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;
}
我们现在了解了如何在差分矩阵中实现一个范围的数据插入,那么我们如何构建差分矩阵呢,我们可以把原矩阵看为空,其本身就是一个差分矩阵,而我们吧矩阵中的元素当作一个个的数据插入,这样我们就实现了差分矩阵的构造。
public static void create(){
for(int i = 0;i<n;i++){
for(int i = 0;i<n;i++){
b[i][j] = insertNum(i,j,i,j,b[i][j]);
}
}
}
接下来就是将差分数组还原为前缀和数组,也是先看下图
假如我们要求的a[i][j],则我们需要将图中紫色的部分和绿色的部分相加,并减去其中重叠的部分,这里就是动态规划的思想,用前面的结果推导出后面的结果,其状态转移方程也就是
a[i][j] = a[i-1][j]+a[i][j-1]-a[i-1][j-1];
所以总结为代码为如下:
public static void returnOriginal(int[][] targetArr,int n) {
for(int i = 0;i<n;i++) {
for(int j = 0;j<n;j++)
if(i==0&&j==0) //第一个数据的处理,因为只有一个数据,所以其前缀和等于其本身
continue;
else if(i==0||j==0) //边界数据,值等于其上方或左方的前缀和加本身
targetArr[i][j] +=targetArr[i==0?i:i-1][j==0?j:j-1];
else//状态转移方程
targetArr[i][j] += targetArr[i-1][j]+targetArr[i][j-1]-targetArr[i-1][j-1];
}
}
题目总结
本题初始的矩阵为0矩阵,则他本身的前缀和矩阵和差分矩阵就相等,而我们就可以把他看为是差分矩阵来直接操作,则我们将题目所输入的m组数据直接插入,最后将m组地毯全部插入后的矩阵还原为前缀和矩阵输出即可,数据的插入就可以优化到O(m),其最终代码为
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String[] one = reader.readLine().split(" ");
int n = Integer.parseInt(one[0]);
int m = Integer.parseInt(one[1]);
int[][] targetArr = new int[n+1][n+1];
String[] loc;
for(int i =0;i<m;i++) {
loc = reader.readLine().split(" ");
insertNum(Integer.parseInt(loc[0])-1, Integer.parseInt(loc[1])-1, Integer.parseInt(loc[2])-1, Integer.parseInt(loc[3])-1, 1, targetArr);
}
returnOriginal(targetArr, n);
for(int i = 0;i<n;i++) {
for(int j = 0;j<n;j++) {
System.out.print(targetArr[i][j]+" ");
}
System.out.println();
}
}
public static void insertNum(int x1,int y1,int x2,int y2,int c,int[][] targetArr) {
targetArr[x1][y1]+=c;
targetArr[x2+1][y1]-=c;
targetArr[x1][y2+1]-=c;
targetArr[x2+1][y2+1]+=c;
}
public static void returnOriginal(int[][] targetArr,int n) {
for(int i = 0;i<n;i++) {
for(int j = 0;j<n;j++)
if(i==0&&j==0)
continue;
else if(i==0||j==0)
targetArr[i][j] +=targetArr[i==0?i:i-1][j==0?j:j-1];
else
targetArr[i][j] += targetArr[i-1][j]+targetArr[i][j-1]-targetArr[i-1][j-1];
}
}
}