差分|前缀和方法解决铺地毯问题-DP二维差分矩阵

题目描述

在 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

说明/提示

样例解释

覆盖第一个地毯后:

00000
01100
01100
00000
00000

覆盖第一、二个地毯后:

00000
01100
01211
00111
00111

覆盖所有地毯后:

01110
01100
01211
00111
00111

数据范围

对于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];
		}
	}
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值