ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

USACO竞赛题解:矩形谷仓扩展算法实现

USACO竞赛题解:矩形谷仓扩展算法实现

1. 题目背景与需求分析

P6081 [USACO05DEC] Barn Expansion G 这道题目出自美国计算机奥林匹克竞赛(USACO)2005年12月月赛的金组题目。题目描述的是农场主有若干个矩形的谷仓,这些谷仓在平面上互不重叠。当农场主想要扩建某个谷仓时,需要判断这个谷仓是否能够向四个方向(上、下、左、右)中的某个方向扩展一个单位,而不会与其他任何谷仓发生重叠。

这道题的核心在于处理二维平面上的矩形几何关系。我们需要设计一个算法,能够高效地判断给定一组不重叠的矩形后,每个矩形是否可以在四个基本方向上进行扩展。这涉及到以下几个关键点:

  1. 矩形表示:每个谷仓可以用其左下角和右上角的坐标来表示
  2. 扩展条件:向某个方向扩展后不能与任何其他矩形相交
  3. 边界情况:扩展后的矩形不能超出给定的平面范围(虽然题目没有明确说明,但通常需要考虑)

2. 数据结构设计与算法思路

2.1 矩形表示与存储

在C++中,我们可以用一个结构体来表示每个谷仓:

struct Barn { int id; // 谷仓编号 int x1, y1; // 左下角坐标 int x2, y2; // 右上角坐标 bool canExpand[4]; // 四个方向是否能扩展:上、右、下、左 };

2.2 关键算法思路

解决这个问题的核心算法可以分为以下几个步骤:

  1. 输入处理:读取所有谷仓的坐标信息
  2. 预处理:对谷仓按照x和y坐标进行排序,便于后续处理
  3. 方向检查:对每个谷仓的四个方向分别检查是否可以扩展
  4. 结果输出:输出可以扩展的谷仓编号

对于方向检查,我们需要考虑:

  • 向上扩展:检查y2+1是否会与其他矩形的y1相交
  • 向右扩展:检查x2+1是否会与其他矩形的x1相交
  • 向下扩展:检查y1-1是否会与其他矩形的y2相交
  • 向左扩展:检查x1-1是否会与其他矩形的x2相交

3. 详细实现与代码解析

3.1 输入处理与初始化

#include <iostream> #include <vector> #include <algorithm> using namespace std; struct Barn { int id; int x1, y1, x2, y2; bool canExpand[4] = {true, true, true, true}; // 上、右、下、左 }; int main() { int N; cin >> N; vector<Barn> barns(N); for(int i = 0; i < N; ++i) { barns[i].id = i + 1; cin >> barns[i].x1 >> barns[i].y1 >> barns[i].x2 >> barns[i].y2; } // 后续处理... }

3.2 检查扩展可能性的核心算法

void checkExpansion(vector<Barn>& barns) { for(int i = 0; i < barns.size(); ++i) { Barn& current = barns[i]; for(int j = 0; j < barns.size(); ++j) { if(i == j) continue; Barn& other = barns[j]; // 检查向上扩展(y2+1)是否可能 if(current.canExpand[0] && current.x1 < other.x2 && current.x2 > other.x1 && current.y2 + 1 > other.y1 && current.y2 + 1 <= other.y2) { current.canExpand[0] = false; } // 检查向右扩展(x2+1)是否可能 if(current.canExpand[1] && current.y1 < other.y2 && current.y2 > other.y1 && current.x2 + 1 > other.x1 && current.x2 + 1 <= other.x2) { current.canExpand[1] = false; } // 检查向下扩展(y1-1)是否可能 if(current.canExpand[2] && current.x1 < other.x2 && current.x2 > other.x1 && current.y1 - 1 < other.y2 && current.y1 - 1 >= other.y1) { current.canExpand[2] = false; } // 检查向左扩展(x1-1)是否可能 if(current.canExpand[3] && current.y1 < other.y2 && current.y2 > other.y1 && current.x1 - 1 < other.x2 && current.x1 - 1 >= other.x1) { current.canExpand[3] = false; } } } }

3.3 结果输出与完整代码

int main() { int N; cin >> N; vector<Barn> barns(N); for(int i = 0; i < N; ++i) { barns[i].id = i + 1; cin >> barns[i].x1 >> barns[i].y1 >> barns[i].x2 >> barns[i].y2; } checkExpansion(barns); for(const auto& barn : barns) { if(barn.canExpand[0] || barn.canExpand[1] || barn.canExpand[2] || barn.canExpand[3]) { cout << barn.id << endl; } } return 0; }

4. 算法优化与性能分析

4.1 时间复杂度分析

当前的实现使用了双重循环来检查每对谷仓之间的关系,时间复杂度为O(N^2),其中N是谷仓的数量。对于USACO竞赛来说,这个复杂度在N≤1000时是可以接受的。

4.2 可能的优化方向

  1. 空间分区:可以使用二维平面分割技术,如四叉树或网格划分,来减少需要检查的谷仓对数量
  2. 扫描线算法:可以尝试使用扫描线算法来更高效地处理矩形之间的关系
  3. 坐标离散化:如果坐标范围很大,可以先进行离散化处理

4.3 边界情况处理

在实际编码中,需要特别注意以下边界情况:

  1. 谷仓紧贴边界的情况
  2. 多个谷仓在同一行或同一列相邻的情况
  3. 谷仓面积为零的情况(虽然题目保证不重叠)
  4. 输入数据量极大的情况(虽然USACO通常给出合理范围)

5. 测试用例设计与验证

5.1 简单测试用例

3 1 1 3 3 4 1 6 3 1 4 3 6

预期输出:

1 2 3

解释:三个谷仓互不干扰,都可以至少向一个方向扩展。

5.2 复杂测试用例

4 1 1 3 3 2 3 4 5 3 1 5 2 4 2 6 4

预期输出:

1 4

解释:

  • 谷仓1可以向左扩展
  • 谷仓2被其他谷仓包围,无法扩展
  • 谷仓3被谷仓1和4限制,无法扩展
  • 谷仓4可以向右扩展

5.3 极端测试用例

2 1 1 1000000 1000000 1 1000000 1000000 1000001

预期输出:

1 2

解释:两个谷仓虽然很大,但只在一点接触,都可以向多个方向扩展。

6. 竞赛技巧与注意事项

6.1 USACO竞赛中的实现技巧

  1. 输入输出效率:USACO对C++的cin/cout效率要求较高,对于大数据量可以考虑使用:

    ios::sync_with_stdio(false); cin.tie(nullptr);
  2. 调试技巧:在本地测试时,可以添加调试输出,但在提交前务必移除或注释掉

  3. 时间管理:先确保正确性,再考虑优化,USACO通常有部分分

6.2 常见错误与避免方法

  1. 坐标比较错误:特别注意边界条件,使用<=还是<
  2. 循环变量混淆:在嵌套循环中容易混淆i和j
  3. 初始化遗漏:确保所有标志位都被正确初始化
  4. 输出格式错误:严格按照题目要求的格式输出

6.3 扩展思考

这道题目可以扩展到更高维度(如3D),或者考虑更复杂的扩展方式(如同时向多个方向扩展)。在实际编程竞赛中,类似的几何问题经常出现,掌握好基本的矩形处理技巧非常重要。

7. 学习资源与进阶路径

7.1 推荐学习资源

  1. USACO官方指南:了解竞赛格式和评分标准
  2. 《算法竞赛入门经典》:包含大量几何问题的解法
  3. 在线判题系统:如Codeforces、LeetCode上的几何题目

7.2 类似题目推荐

  1. USACO 2011 Open Gold: "Fenced In"
  2. Codeforces 1408E: "Avoid Rainbow Cycles"
  3. LeetCode 391: "Perfect Rectangle"

7.3 学习路径建议

  1. 先掌握基础的几何概念和表示方法
  2. 练习简单的矩形相交/包含判断
  3. 逐步解决更复杂的几何问题
  4. 参加在线比赛积累实战经验

在实际解决这类问题时,我通常会先画图理清几何关系,然后设计数据结构表示这些几何对象,最后考虑如何高效地处理它们之间的关系。对于初学者来说,从简单的二维几何问题入手是个不错的选择,随着经验的积累,可以逐步挑战更复杂的三维或更高维问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进