题目描述
题目描述:
平面内有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分,请问他最晚可以什么时间起床