题解:洛谷 P4122 Blocked Billboard
【题目来源】
洛谷:P4122 [USACO17DEC] Blocked Billboard B - 洛谷
【题目描述】
在漫长的挤奶过程中,奶牛 Bessie 喜欢透过谷仓的窗户盯着街对面的两块巨大的矩形广告牌,上面分别写着“Farmer Alex 的美味苜蓿”和“Farmer Greg 的优质谷物”。广告牌上这两种奶牛饲料的图片对 Bessie 来说比她农场里的草看起来美味得多。
有一天,当 Bessie 正盯着窗外时,她惊讶地看到一辆巨大的矩形卡车停在街对面。卡车的侧面有一则广告,写着“Farmer Smith 的顶级牛排”,Bessie 不太理解这则广告,但她更担心的是卡车可能会挡住她最喜欢的两块广告牌的视线。
给定两块广告牌和卡车的位置,请计算两块广告牌仍然可见的总面积。卡车可能遮挡了其中一块、两块,或者没有遮挡任何一块广告牌。
【输入】
输入的第一行包含四个用空格分隔的整数:\(x_1,y_1,x_2,y_2\),其中 \((x_1,y_1)\) 和 \((x_2,y_2)\) 是 Bessie 的二维视野中第一块广告牌的左下角和右上角坐标。第二行包含四个整数,以相同的方式指定第二块广告牌的左下角和右上角坐标。第三行也是最后一行输入包含四个整数,指定卡车的左下角和右上角坐标。所有坐标都在 \(-1000\) 到 \(+1000\) 的范围内。保证两块广告牌之间没有任何重叠的正面积区域。
【输出】
请输出两块广告牌仍然可见的总面积。
【输入样例】
1 2 3 5
6 0 10 4
2 1 8 3
【输出样例】
17
【核心思想】
-
问题分析:给定两块矩形广告牌(左下角与右上角坐标)和一块矩形卡车(覆盖物),求两块广告牌未被卡车遮挡的可见总面积。本质是一个矩形面积覆盖问题,需要计算两个矩形并集减去与第三个矩形重叠部分后的剩余面积。
-
算法选择:
- 离散化 + 网格扫描(解法一):将所有矩形的边界坐标离散化后,将平面划分为若干小网格单元,逐一判断每个网格是否属于广告牌但不在卡车内
- 直接几何公式法(解法二):通过坐标比较,直接计算每块广告牌与卡车的重叠矩形面积,再用总面积减去重叠面积
-
关键步骤(以离散化方法为例):
- 收集边界坐标:将三个矩形的所有 \(x\) 坐标(左、右边界)存入数组 \(x[1..6]\),所有 \(y\) 坐标(下、上边界)存入数组 \(y[1..6]\)
- 排序离散化:对 \(x\) 数组和 \(y\) 数组分别排序,相邻坐标之间形成网格单元的边长
- 网格遍历:遍历所有 \(5 \times 5\) 网格单元(由6个 \(x\) 坐标和6个 \(y\) 坐标划分而成)
- 判定归属:对每个网格单元 \((i, j)\),判断其是否完全包含在广告牌1或广告牌2内,且不完全包含在卡车内
- 面积累加:若满足条件,将网格面积 \((x_{i+1} - x_i) \times (y_{j+1} - y_j)\) 累加到答案
-
时间/空间复杂度:
- 时间复杂度:\(O(1)\)(固定 \(6 \times 6\) 坐标,\(5 \times 5\) 网格,常数级遍历)
- 空间复杂度:\(O(1)\)(固定大小数组,与输入规模无关)
-
离散化与网格扫描的核心思想:
- 降维打击:将连续的二维平面通过边界坐标离散化为有限个网格单元,把复杂的几何重叠判断转化为简单的点包含判定
- 完全包含判定:判断网格单元是否在某个矩形内时,只需检查网格的四个边界是否都在矩形边界内,而非逐点扫描
- 容斥原理的替代:不需要显式计算重叠区域面积,而是通过"属于A且不属于B"的逻辑直接统计可见部分
- 坐标压缩:仅保留所有矩形的边界坐标,忽略无意义的空白区域,大幅减少计算量
- 适用于小范围坐标(如 \([-1000, 1000]\))的矩形覆盖、面积并集、遮挡类问题
【解题思路】

【算法标签】
普及- #数学
【代码详解】
// 使用离散化方法求解
#include <bits/stdc++.h>
using namespace std;
int a[5], b[5], c[5]; // 存储三个矩形的坐标:a[广告牌1], b[广告牌2], c[覆盖物]
int ans = 0; // 存储未被覆盖的总面积
int x[7], y[7]; // 存储所有x坐标和y坐标(用于网格划分)
/**
* 判断网格单元是否完全包含在矩形内部
* @param i x方向网格索引
* @param j y方向网格索引
* @param k 矩形的坐标数组
* @return 如果网格单元完全在矩形内返回true,否则false
*/
bool in(int i, int j, int k[])
{
// 检查网格单元是否完全包含在矩形内
// 网格单元的四个角必须在矩形边界内
if (x[i] >= k[1] && x[i + 1] <= k[3] &&
y[j] >= k[2] && y[j + 1] <= k[4])
{
return true;
}
return false;
}
int main()
{
// 输入第一个广告牌的坐标(左下角和右上角)
for (int i = 1; i <= 4; i++)
{
cin >> a[i];
}
// 输入第二个广告牌的坐标(左下角和右上角)
for (int i = 1; i <= 4; i++)
{
cin >> b[i];
}
// 输入覆盖物的坐标(左下角和右上角)
for (int i = 1; i <= 4; i++)
{
cin >> c[i];
}
// 收集所有x坐标(三个矩形的左右边界)
x[1] = a[1]; // 广告牌1左边界
x[2] = a[3]; // 广告牌1右边界
x[3] = b[1]; // 广告牌2左边界
x[4] = b[3]; // 广告牌2右边界
x[5] = c[1]; // 覆盖物左边界
x[6] = c[3]; // 覆盖物右边界
// 收集所有y坐标(三个矩形的上下边界)
y[1] = a[2]; // 广告牌1下边界
y[2] = a[4]; // 广告牌1上边界
y[3] = b[2]; // 广告牌2下边界
y[4] = b[4]; // 广告牌2上边界
y[5] = c[2]; // 覆盖物下边界
y[6] = c[4]; // 覆盖物上边界
// 对坐标进行排序,用于网格划分
sort(x + 1, x + 6 + 1);
sort(y + 1, y + 6 + 1);
// 遍历所有网格单元(5x5网格)
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= 5; j++)
{
// 检查网格单元是否在广告牌1或广告牌2内,但不在覆盖物内
if ((in(i, j, a) && !in(i, j, c)) ||
(in(i, j, b) && !in(i, j, c)))
{
// 计算未被覆盖的网格单元面积并累加
ans += (y[j + 1] - y[j]) * (x[i + 1] - x[i]);
}
}
}
// 输出未被覆盖的总面积
cout << ans << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
// 三个矩形的坐标:广告牌1、广告牌2、覆盖物
int x11, x12, y11, y12; // 广告牌1:左下角(x11,y11),右上角(x12,y12)
int x21, x22, y21, y22; // 广告牌2:左下角(x21,y21),右上角(x22,y22)
int x31, x32, y31, y32; // 覆盖物:左下角(x31,y31),右上角(x32,y32)
int mj1, mj2, ans; // mj1:广告牌1被遮挡面积, mj2:广告牌2被遮挡面积, ans:剩余可见面积
int main()
{
// 输入广告牌1的坐标
cin >> x11 >> y11 >> x12 >> y12;
// 输入广告牌2的坐标
cin >> x21 >> y21 >> x22 >> y22;
// 输入覆盖物的坐标
cin >> x31 >> y31 >> x32 >> y32;
int tmp1, tmp2, tmp3, tmp4; // 临时变量(未使用)
// 确保广告牌1在广告牌2的左边(x11 <= x21)
if (x11 > x21)
{
swap(x11, x21);
swap(y11, y21);
swap(x12, x22);
swap(y12, y22);
}
// 特殊情况1:覆盖物完全覆盖两个广告牌
if (x31 <= x11 && x32 >= x22 && y31 <= y11 && y32 >= y22)
{
cout << 0 << endl; // 完全被覆盖,可见面积为0
return 0;
}
// 特殊情况2:覆盖物只覆盖广告牌1
if (x31 <= x11 && x32 <= x22 && y31 <= y11 && y32 >= y12)
{
// 只有广告牌2可见
cout << abs(y22 - y21) * abs(x22 - x21) << endl;
return 0;
}
// 特殊情况3:覆盖物只覆盖广告牌2
if (x31 >= x12 && x31 <= x22 && y31 <= y21 && y32 >= y22)
{
// 只有广告牌1可见
cout << abs(y12 - y11) * abs(x12 - x11) << endl;
return 0;
}
// 特殊情况4:覆盖物与两个广告牌无重叠
if (x31 >= x22 || x32 <= x11)
{
// 两个广告牌都完全可见
cout << abs(y12 - y11) * abs(x12 - x11) + abs(y22 - y21) * abs(x22 - x21) << endl;
return 0;
}
// 特殊情况5:覆盖物完全在某个广告牌内部
if ((y12 >= y32 && y31 >= y11 && x12 >= x32 && x31 >= x11) ||
(y22 >= y32 && y31 >= y21 && x22 >= x32 && x31 >= x21))
{
// 总面积减去覆盖物面积
cout << abs(y12 - y11) * abs(x12 - x11) + abs(y22 - y21) * abs(x22 - x21) -
abs(y32 - y31) * abs(x32 - x31) << endl;
return 0;
}
// 计算广告牌1与覆盖物的重叠面积(6种情况)
if (y12 >= y32 && y11 >= y31 && x32 >= x12 && x31 >= x11)
{
// 情况1:覆盖物在广告牌1右上方
mj1 = abs(y32 - y11) * abs(x12 - x31);
}
else if (y32 >= y12 && y31 >= y11 && x32 >= x12 && x31 >= x11)
{
// 情况2:覆盖物在广告牌1右下方
mj1 = abs(y12 - y31) * abs(x12 - x31);
}
else if (y32 >= y12 && y31 >= y11 && x32 >= x12 && x11 >= x31)
{
// 情况3:覆盖物在广告牌1左下方
mj1 = abs(y12 - y31) * abs(x12 - x11);
}
else if (y12 >= y32 && y11 >= y31 && x32 >= x12 && x11 >= x31)
{
// 情况4:覆盖物在广告牌1左上方
mj1 = abs(y32 - y11) * abs(x12 - x11);
}
else if (y12 >= y32 && y31 >= y11 && x32 >= x12 && x31 >= x11)
{
// 情况5:覆盖物在广告牌1右侧中间
mj1 = abs(y32 - y31) * abs(x12 - x31);
}
else if (y12 >= y32 && y31 >= y11 && x32 >= x12 && x11 >= x31)
{
// 情况6:覆盖物在广告牌1左侧中间
mj1 = abs(y32 - y31) * abs(x12 - x11);
}
// 计算广告牌2与覆盖物的重叠面积(6种情况)
if (y22 >= y32 && y21 >= y31 && x22 >= x32 && x21 >= x31)
{
// 情况1:覆盖物在广告牌2右上方
mj2 = abs(y32 - y21) * abs(x32 - x21);
}
else if (y32 >= y22 && y31 >= y21 && x22 >= x32 && x21 >= x31)
{
// 情况2:覆盖物在广告牌2右下方
mj2 = abs(y22 - y31) * abs(x32 - x21);
}
else if (y32 >= y22 && y31 >= y21 && x32 >= x22 && x21 >= x31)
{
// 情况3:覆盖物在广告牌2左下方
mj2 = abs(y22 - y31) * abs(x22 - x21);
}
else if (y22 >= y32 && y21 >= y31 && x32 >= x22 && x21 >= x31)
{
// 情况4:覆盖物在广告牌2左上方
mj2 = abs(y32 - y21) * abs(x22 - x21);
}
else if (y22 >= y32 && y31 >= y21 && x22 >= x32 && x21 >= x31)
{
// 情况5:覆盖物在广告牌2右侧中间
mj2 = abs(y32 - y31) * abs(x32 - x21);
}
else if (y22 >= y32 && y31 >= y21 && x32 >= x22 && x21 >= x31)
{
// 情况6:覆盖物在广告牌2左侧中间
mj2 = abs(y32 - y31) * abs(x22 - x21);
}
// 计算总面积和剩余可见面积
int tot = abs(y12 - y11) * abs(x12 - x11) + abs(y22 - y21) * abs(x22 - x21);
ans = tot - mj1 - mj2;
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
1 2 3 5
6 0 10 4
2 1 8 3
17
浙公网安备 33010602011771号