题解:洛谷 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

【核心思想】

  1. 问题分析:给定两块矩形广告牌(左下角与右上角坐标)和一块矩形卡车(覆盖物),求两块广告牌未被卡车遮挡的可见总面积。本质是一个矩形面积覆盖问题,需要计算两个矩形并集减去与第三个矩形重叠部分后的剩余面积。

  2. 算法选择

    • 离散化 + 网格扫描(解法一):将所有矩形的边界坐标离散化后,将平面划分为若干小网格单元,逐一判断每个网格是否属于广告牌但不在卡车内
    • 直接几何公式法(解法二):通过坐标比较,直接计算每块广告牌与卡车的重叠矩形面积,再用总面积减去重叠面积
  3. 关键步骤(以离散化方法为例):

    • 收集边界坐标:将三个矩形的所有 \(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)\) 累加到答案
  4. 时间/空间复杂度

    • 时间复杂度:\(O(1)\)(固定 \(6 \times 6\) 坐标,\(5 \times 5\) 网格,常数级遍历)
    • 空间复杂度:\(O(1)\)(固定大小数组,与输入规模无关)
  5. 离散化与网格扫描的核心思想

    • 降维打击:将连续的二维平面通过边界坐标离散化为有限个网格单元,把复杂的几何重叠判断转化为简单的点包含判定
    • 完全包含判定:判断网格单元是否在某个矩形内时,只需检查网格的四个边界是否都在矩形边界内,而非逐点扫描
    • 容斥原理的替代:不需要显式计算重叠区域面积,而是通过"属于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
posted @ 2026-08-29 09:37  团爸讲算法  阅读(4)  评论(0)    收藏  举报