题解:洛谷 P1034 矩形覆盖
【题目来源】
洛谷:P1034 [NOIP 2002 提高组] 矩形覆盖 - 洛谷
【题目描述】
在平面上有 \(n\) 个点,每个点用一对整数坐标表示。例如:当 \(n\)\(=\)\(4\) 时,\(4\) 个点的坐标分别为:\(p_1(1,1)\),\(p_2(2,2)\),\(p_3(3,6)\),\(p_4(0,7)\),见图一。

这些点可以用 \(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
【核心思想】
-
问题分析:给定平面上 \(n\) 个点,需用 \(k\) 个边平行于坐标轴的矩形覆盖所有点,要求矩形之间完全不相交(边和顶点都不能重合),最小化 \(k\) 个矩形的面积之和。这是一个深度优先搜索 + 剪枝问题,关键在于枚举每个点归属哪个矩形,用 DFS 搜索所有分配方案,并通过当前面积剪枝和最终重叠检查筛选合法解。
-
算法选择:
- DFS 枚举分配:每个点有 \(k\) 种选择(放入 \(k\) 个矩形之一),搜索所有 \(k^n\) 种分配方案
- 矩形动态维护:每个矩形维护 \(x_{\min}, x_{\max}, y_{\min}, y_{\max}\),加入点时动态更新边界和面积
- 回溯恢复状态:递归前保存矩形状态,递归后恢复,保证搜索树的正确性
- 剪枝优化:当前总面积 \(\geq\) 已知最优解时立即返回
- 重叠检查:所有点分配完毕后,\(O(k^2)\) 检查任意两个矩形是否相交
-
关键步骤:
- 初始化:读入 \(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\) 的状态(回溯)
- 保存矩形 \(i\) 的当前状态
- 输出:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(k^n \cdot k^2)\),每个叶子节点 \(O(k^2)\) 重叠检查,剪枝可大幅减少实际搜索量
- 空间复杂度:\(O(k)\),递归栈深度 \(O(n)\),矩形数组 \(O(k)\)
-
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
浙公网安备 33010602011771号