每日一题-矩形重叠

题目描述

本题来自牛客网,为网易2019年校招题之一。(点我跳转)

题目描述:
平面内有n个矩形, 第i个矩形的左下角坐标为(x1[i], y1[i]), 右上角坐标为(x2[i], y2[i])。
如果两个或者多个矩形有公共区域则认为它们是相互重叠的(不考虑边界和角落)。
请你计算出平面内重叠矩形数量最多的地方,有多少个矩形相互重叠。
输入描述:
输入包括五行。
第一行包括一个整数n(2 <= n <= 50), 表示矩形的个数。
第二行包括n个整数x1[i](-10^9 <= x1[i] <= 10^9),表示左下角的横坐标。
第三行包括n个整数y1[i](-10^9 <= y1[i] <= 10^9),表示左下角的纵坐标。
第四行包括n个整数x2[i](-10^9 <= x2[i] <= 10^9),表示右上角的横坐标。
第五行包括n个整数y2[i](-10^9 <= y2[i] <= 10^9),表示右上角的纵坐标。
输出描述:
输出一个正整数, 表示最多的地方有多少个矩形相互重叠,如果矩形都不互相重叠,输出1。
时间限制:1秒 空间限制:32768K

题目分析

面对这个问题,如果采用穷举法,依次检查两两矩形之间有无重叠部分,那么时间复杂度将变成O(n2)。如果依次检查区域上每一点是否有矩形覆盖,则时间复杂度为o(n*m2).
这明显会有超时的风险,因此我们必须优化算法。而由于矩形之间与区域的点之间存在离散结构,那么可以将其分块处理。这便是分治的思想。
在这里,分治法有两条路可以走。
第一条,便是将区域分块处理并且统计分块内的矩形个数,并依次对分块排序。从矩形个数最大的分块开始继续分块。
若分块足够小,则依次迭代分块中的每一点并且放回最大的矩形重叠数。
若返回的最大矩形数大于接下来分块的矩形个数则返回上一级。
第二条,则是将二维降为一维。把矩形分别投影在x,y轴上。依次迭代矩形,根据矩形的投影范围将x,y轴分区并且统计分区中矩形的个数。
通过数学证明可以证得,若有多个矩形在x轴上的某一区域上有投影的同时也在y轴上某一区域内有投影,那么可以认为这些矩形有相互重叠的面积。
接下来便以这条方法为思路提出解决方案。

解决方案

首先,先建立一个结构体来记录矩形投影的信息。

struct rec {//rec:矩形的投影
	int x;//
	int y;
	int no;//no为矩形的编号
	rec()
	{
		x = 0;
		y = 0;
		no = 0;
	}
};

再建立一个类来记录x轴或者y轴上的分区。该类要求记录分区的区域的同时能记录其上投影的矩形。

struct range {
	int x;
	int y;
	set<int>* rec;
	range()
	{
		x = 0;
		y = 0;
		rec = new set<int>();
	}
};

由于我们需要将矩形分别投影在x,y轴上,那么可以将这一部分功能提取成函数。

void overrec(int* x, int* y, int num, vector<range>& rg);

在这个函数中,输入是矩形在数轴上投影的范围,矩形的个数与一个空的range的容器。
而输出表便是引用的range容器。
首先,创建一个容器储存矩形在数轴上的投影。在这里,容器选用基于堆实现的优先级队,因为其具有自排序性且操作的时间复杂的较低。不过在此之前,需要重载rec的大于运算符。

...
bool operator<(const rec& o) const
{
	return x < o.x;
}
...
priority_queue<rec> pr;
rec temp;
for (i = 0; i < num; i++)
{
	temp.x = y[i];
	temp.y = x[i];
	temp.no = i;
	pr.push(temp);
}

由于priority_queue不具备begin()函数,因此采用for循环从大到小迭代,依次创建range。

auto ver = pr.top();
	for (i = 0; i < num; i++)
	{
		ver = pr.top();
		pr.pop();
		...
	}

在这里,可以以当前rec的x为标记点,假设在x之后的所有矩形已经都遍历过。创建一个容器yQueue,储存在当前标记点有投影的矩形。每迭代一个矩形就遍历一遍yQueue,记录当前投影重叠的矩形。

vector<pair<int, int>> yQueue;
pair<int, int> t_p;
less<pair<int, int>> lpii;
...
auto ver = pr.top();
for (i = 0; i < num; i++)
{
	ver = pr.top();
	pr.pop();
	t_p.first = ver.y;
	t_p.second = ver.no;
	yQueue.push_back(t_p);
	push_heap(yQueue.begin(), yQueue.end(), lpii);
	for (auto yver : yQueue)
	{
		if (yver.first < ver.x) break;
		pop_heap(yQueue.begin(), yQueue.end(), lpii);
		yQueue.pop_back();
	}
	t_p = yQueue.front();
	t_y = t_p.first;
	if (t_y < ver.x)
	{
		auto cur = new range();
		cur->x = ver.x;
		cur->y = t_y;
		for (auto yver : yQueue)
		{
			cur->rec->insert(yver.second);
		}
		rg.push_back(*cur);
	}
}

setdiff为求两个集合中相同元素的个数

int setdiff(set<int>* set_a, set<int>* set_b)
{
	auto a = set_a->begin();
	auto b = set_b->begin();
	int sdn=0;
	while (a != set_a->end() && b != set_b->end())
	{
		if (*a>*b)
		{
			b++;
		}
		else if (*a < * b)
		{
			a++;
		}
		else
		{
			a++;
			b++;
			sdn++;
		}
	}
	return sdn;
}

通过迭代获得的range容器,取每个x,y上的区域的交集

overrec(x1, x2, num, rg_x);
overrec(y1, y2, num, rg_y);
int ans = 0, xrs;
for (auto x : rg_x)
	{
		for (auto y : rg_y)
		{
			xrs = setdiff(x.rec, y.rec);
			ans = ans > xrs ? ans : xrs;
		}
	}

在这个函数中输入便是x轴与y轴上range的容器。
这函数输出的便是x,y上两区域中最大重叠的矩形个数,也就是本题的答案

明天的题

牛牛总是睡过头,所以他定了很多闹钟,只有在闹钟响的时候他才会醒过来并且决定起不起床。从他起床算起他需要X分钟到达教室,上课时间为当天的A时B分,请问他最晚可以什么时间起床

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值