洛谷 —— 地毯题解(教会你差分算法)

题目描述

在 n×n 的格子上有 m 个地毯。

给出这些地毯的信息,问每个点被多少个地毯覆盖。

输入格式

第一行,两个正整数 n,m。意义如题所述。

接下来 m 行,每行两个坐标 (x1,y1) 和 (x2​,y2​),代表一块地毯,左上角是 (x1​,y1​),右下角是(x2,y2​)。

输出格式

输出 n 行,每行 n 个正整数。

第 i 行第 j 列的正整数表示 (i,j)这个格子被多少个地毯覆盖。

原题链接如下:

P3397 - 地毯icon-default.png?t=MBR7https://www.luogu.com.cn/problem/P3397

解题思路:

暴力方法解决:

        这题的解题思路还是很清晰的,既然地毯的范围都告诉你了,那有一个很简单且暴力的方法就是每次给定地毯的大小之后使范围内数组的值全部加一,经过m次这样的操作之后,再把数组中的值打印出来即可,但是如果这样做的话,每次使地毯内的值加一的时间复杂度是o(n^2),那么整个程序的时间复杂度就是o(mn^2+n^2),题目的数据范围是n,m<=1000,那如果是<=10000,甚至是更大呢,那么这么大的时间复杂度就显然不能满足所需,那么有没有更好的方法呢?答案肯定是有的,这里先附上暴力算法的代码,然后接下来就来讲解一下更好的算法。

#include<stdio.h>
int grid[1002][1002];
//使用全局变量定义数组是因为1002*1002所占用的空间太大,定义为局部变量容易爆栈
void cover( int x1, int y1, int x2, int y2)
{
    int i = 0;
	int j = 0;
	for (i = x1; i <= x2; i++)
	{
		for (j = y1 ; j <= y2; j++)
			++grid[i][j];
	}
}

int main()
{
	int n = 0,m = 0;
	scanf("%d %d", &n,&m);
	int  i = 0,j = 0;
	int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	for (i = 0; i < m; i++)
	{
		scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
		cover(x1, y1, x2, y2);
	}
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			printf("%d ", grid[i][j]);
		}
		printf("\n");
	}
	return 0;
}

注:为什么题目要求是n,m<=1000,而二维数组要定义成grid[1002][1002] ?

        这是由于数组下标由0开始,为了更好管理下标,我们给数组加一个边框,边框内的数据没有价值。

差分算法:

        那么,什么是差分呢?为什么用差分能降低时间复杂度,更有效率呢?这就是下面的内容将会说明的问题。

        我们来考虑铺地毯问题的一维版本,给你n个点,并且给你m条线段,求这n个点各自有几条线段穿过,一种方法就是暴力,那么差分是怎样做的呢?其实我们不需要知道每条线段具体都经过了几个点,而只需要标记两端就够了,然后从左到右将标记相加就能得到对应位置的答案,绳子两端之间的点都在这条绳子的范围内,通过打标记这种o(1)时间复杂度的方式取代暴力的o(n)复杂度的遍历,这就是差分方法的精髓也是为什么差分能降低时间复杂度的原因。

        给个一维数组的例题:假设要在[1,5]和[2,3]范围内加1,请输出每个位置上的数的大小。

第一步:在1的位置打上+1的标记,在终点的下一个位置(6)打上-1的标记(通过抵消前面线段起点标记(+1)的形式代表线段的中止),2和3同理

 

        第二步:从左向右遍历数组,每个下标对应的数就是前缀和(前面所有标记的总和),在得出答案的同时将答案输出即可。

for(int i = 1;i<=n;i++)
    printf("%d ",arr[i][j]+=arr[i-1][j]);

        这就是在一维数组中使用差分的方法,那么有了这个基础后,我们就可以将差分的方法运用到二维数组中了,我们可以将二维数组当作n个一维数组组成,那么这个题目中告诉了我们地毯的左上角坐标和右下角坐标,根据这个信息我们就可以在地毯的左右两边都做上标记,如下所示:

0 +1 0 0 -1 0

0 +1 0 0 -1 0

0 +1 0 0 -1 0

0 +1 0 0 -1 0

        这样的话就把暴力算法中的遍历地毯范围增加值的时间复杂度由o(n^2)变成了o(n),提高了效率,在输出答案是就输出每行的前缀和就行了。代码如下:

#include<stdio.h>
int grid[1002][1002];
void cover(int x1, int y1, int x2, int y2)
{
	int i = 0;
	for (i = x1; i <= x2; i++)
	{
    //对地毯每一行的两端进行标记
		++grid[i][y1];
		--grid[i][y2 + 1];
	}
}

int main()
{
	int n = 0, m = 0;
	scanf("%d %d", &n, &m);
	int  i = 0, j = 0;
	int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
	for (i = 0; i < m; i++)
	{
		scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
		cover(x1, y1, x2, y2);
	}
	for (i = 1; i <= n; i++)
	{
		for (j = 1; j <= n; j++)
		{
			printf("%d ", grid[i][j]+=grid[i][j-1]);
            //输出每个元素的前缀和
		}
		printf("\n");
	}
	return 0;
}

 

        运用该算法的时间复杂度是o(mn+n^2),这样如果给的数据更大也没问题了,当然这道题还不止可以用这种方法解答,社区里的大佬还提到了二维线段树或二维树状数组的方法解答,这种方法单次操作的时间复杂度是o((logn)^2),但是感觉有点大材小用了(其实是我不会doge),以上就是这道题的题解啦!如果还有问题的话,可以评论区上探讨。

 

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

暮雨清秋.L

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

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

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

打赏作者

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

抵扣说明:

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

余额充值