题解:洛谷 P1034 矩形覆盖

【题目来源】

洛谷:P1034 [NOIP 2002 提高组] 矩形覆盖 - 洛谷

【题目描述】

在平面上有 \(n\) 个点,每个点用一对整数坐标表示。例如:当 \(n\)\(=\)\(4\) 时,\(4\) 个点的坐标分别为:\(p_1(1,1)\)\(p_2(2,2)\)\(p_3(3,6)\)\(p_4(0,7)\),见图一。

img/Pasted image 20260808195914.png

这些点可以用 \(k\) 个矩形全部覆盖,矩形的边平行于坐标轴。当 \(k\)\(=\)\(2\) 时,可用如图二的两个矩形 \(s_1,s_2\) 覆盖,\(s_1,s_2\) 面积和为 \(4\)。问题是当 \(n\) 个点坐标和 \(k\) 给出后,怎样才能使得覆盖所有点的 \(k\) 个矩形的面积之和为最小呢?
约定:覆盖一个点的矩形面积为 \(0\);覆盖平行于坐标轴直线上点的矩形面积也为 \(0\)。各个矩形必须完全分开(边线与顶点也都不能重合)。

【输入】

第一行共两个整数 \(n,k\),含义如题面所示。

接下来 \(n\) 行,其中第 \(i+1\) 行有两个整数 \(x_i,y_i\),表示平面上第 \(i\) 个点的坐标。

【输出】

共一行一个整数,为满足条件的最小的矩形面积之和。

【输入样例】

4 2
1 1
2 2
3 6
0 7

【输出样例】

4

【核心思想】

  1. 问题分析:给定平面上 \(n\) 个点,需用 \(k\) 个边平行于坐标轴的矩形覆盖所有点,要求矩形之间完全不相交(边和顶点都不能重合),最小化 \(k\) 个矩形的面积之和。这是一个深度优先搜索 + 剪枝问题,关键在于枚举每个点归属哪个矩形,用 DFS 搜索所有分配方案,并通过当前面积剪枝和最终重叠检查筛选合法解。

  2. 算法选择

    • DFS 枚举分配:每个点有 \(k\) 种选择(放入 \(k\) 个矩形之一),搜索所有 \(k^n\) 种分配方案
    • 矩形动态维护:每个矩形维护 \(x_{\min}, x_{\max}, y_{\min}, y_{\max}\),加入点时动态更新边界和面积
    • 回溯恢复状态:递归前保存矩形状态,递归后恢复,保证搜索树的正确性
    • 剪枝优化:当前总面积 \(\geq\) 已知最优解时立即返回
    • 重叠检查:所有点分配完毕后,\(O(k^2)\) 检查任意两个矩形是否相交
  3. 关键步骤

    • 初始化:读入 \(n, k\)\(n\) 个点的坐标
    • DFS 搜索(参数 \(x\) 表示已处理前 \(x\) 个点):
      • 剪枝:若当前总面积 \(\geq ans\),直接返回
      • 终止条件\(x = n\) 时,检查所有矩形是否互不重叠,若合法则更新 \(ans\)
      • 枚举分配:尝试将第 \(x+1\) 个点放入矩形 \(i\)\(1 \leq i \leq k\)):
        • 保存矩形 \(i\) 的当前状态 tmp
        • addPoint(i, x+1):更新矩形 \(i\) 的边界和面积
        • dfs(x+1):递归处理下一个点
        • sf[i] = tmp:恢复矩形 \(i\) 的状态(回溯)
    • 输出\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(k^n \cdot k^2)\),每个叶子节点 \(O(k^2)\) 重叠检查,剪枝可大幅减少实际搜索量
    • 空间复杂度:\(O(k)\),递归栈深度 \(O(n)\),矩形数组 \(O(k)\)
  5. DFS + 剪枝的核心思想

    • 状态空间枚举:将"点归属哪个矩形"的分配问题转化为 \(k\) 叉搜索树,DFS 系统遍历所有可能
    • 增量式面积计算:矩形边界和面积随点的加入动态更新,避免每次重新计算所有点
    • 最优性剪枝:当前总面积已不小于已知最优解时,后续加入点只会使面积增大或不变,不可能得到更优解,直接剪枝
    • 合法性延迟检查:重叠约束在叶子节点统一检查,而非每次加入点都检查,减少中间状态判断开销
    • 回溯的正确性:通过保存/恢复矩形状态,确保搜索树各分支独立,不互相污染
    • 适用于"组合分配 + 约束满足"的小规模搜索问题,核心在于状态表示、剪枝策略和回溯机制

【算法标签】

普及+ #DFS-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 105;      // 定义最大点数和最大矩形数

int n;                  // 点的数量
int k;                  // 矩形的数量
int a[N], b[N];         // 存储每个点的x和y坐标
int ans = 1e9;          // 存储最终结果,初始化为极大值

// 矩形结构体
struct Square
{
    int x_min, x_max;   // x轴最小最大值
    int y_min, y_max;   // y轴最小最大值
    int area;           // 矩形面积
    int idx;            // 矩形编号
    bool flag;          // 标记是否已初始化
} sf[5];                // 最多5个矩形

// 计算所有矩形总面积
int getArea()
{
    int res = 0;
    for (int i = 1; i <= k; i++)
        res += sf[i].area;
    return res;
}

// 检查两个矩形是否重叠
bool check2(int x, int y)
{
    if (sf[x].x_min > sf[y].x_max || sf[x].x_max < sf[y].x_min ||
        sf[x].y_min > sf[y].y_max || sf[x].y_max < sf[y].y_min)
        return false;
    return true;
}

// 检查所有矩形是否都不重叠
bool check()
{
    for (int i = 1; i < k; i++)
    {
        for (int j = i + 1; j <= k; j++)
        {
            if (sf[i].flag == false || sf[j].flag == false)
                continue;
            if (check2(i, j))
                return false;
        }
    }
    return true;
}

// 将点加入指定矩形
void addPoint(int x, int y)
{
    if (sf[x].flag == false)  // 如果矩形未初始化
    {
        sf[x].x_max = sf[x].x_min = a[y];
        sf[x].y_max = sf[x].y_min = b[y];
        sf[x].flag = true;
        sf[x].idx = x;
    }
    else  // 如果矩形已存在
    {
        sf[x].x_max = max(sf[x].x_max, a[y]);
        sf[x].x_min = min(sf[x].x_min, a[y]);
        sf[x].y_max = max(sf[x].y_max, b[y]);
        sf[x].y_min = min(sf[x].y_min, b[y]);
        sf[x].area = (sf[x].x_max - sf[x].x_min) * (sf[x].y_max - sf[x].y_min);
    }
    return;
}

// 深度优先搜索
void dfs(int x)
{
    int s = getArea();
    if (s >= ans)  // 剪枝:如果当前面积已经大于已知最小面积
        return;

    if (x == n)    // 所有点都已处理
    {
        if (check())  // 检查矩形是否都不重叠
            ans = s;  // 更新最小面积
        return;
    }

    // 尝试将下一个点加入各个矩形
    for (int i = 1; i <= k; i++)
    {
        Square tmp = sf[i];  // 保存当前矩形状态
        addPoint(i, x + 1);  // 将点加入矩形
        dfs(x + 1);          // 递归处理下一个点
        sf[i] = tmp;         // 恢复矩形状态
    }
}

int main()
{
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i] >> b[i];  // 输入每个点的坐标

    dfs(0);  // 从第0个点开始搜索
    cout << ans << endl;  // 输出最小总面积

    return 0;
}

【运行结果】

4 2
1 1
2 2
3 6
0 7
4
posted @ 2026-08-10 12:24  团爸讲算法  阅读(13)  评论(0)    收藏  举报