1. 题目解析与算法思路
这道蓝桥杯算法提高VIP题目"铺地毯"看似简单,实则考察了选手对二维空间处理的综合能力。题目大意是给定一个矩形区域和若干块地毯的铺设信息(每块地毯由其左下角和右上角坐标确定),最后需要查询某个点被多少块地毯覆盖。
1.1 核心问题建模
这个问题可以抽象为二维平面的区间覆盖问题。假设有一个n×m的矩形区域,我们依次铺设k块地毯,每块地毯覆盖一个子矩形区域。铺设完成后,给出一个查询点(x,y),要求返回该点被多少块地毯覆盖。
关键点在于:
- 地毯铺设顺序影响最终结果(后铺设的地毯会覆盖之前的地毯)
- 需要高效处理大量查询(暴力解法在数据量大时性能不足)
1.2 算法选择分析
对于这类问题,常见的解法有:
-
暴力解法:存储所有地毯信息,查询时遍历所有地毯检查是否覆盖该点
- 时间复杂度:O(k) per query
- 空间复杂度:O(k)
- 优点:实现简单
- 缺点:当k很大时(如1e5),查询效率低
-
差分数组优化:
- 对x和y方向分别建立差分数组
- 预处理阶段:O(k)
- 查询阶段:O(1)
- 优点:查询极快
- 缺点:空间复杂度高(O(nm)),当n,m很大时(如1e5)内存不足
-
扫描线算法:
- 将问题转化为一维区间覆盖问题
- 时间复杂度:O(k log k)
- 空间复杂度:O(k)
- 优点:适合大规模数据
- 缺点:实现较复杂
根据蓝桥杯的题目特点(通常k≤1e4),暴力解法在大多数情况下已经足够,且实现简单不易出错。下面我们重点讲解暴力解法的实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 暴力解法实现详解
2.1 数据结构设计
首先需要存储每块地毯的信息。每块地毯可以用其左下角(a,b)和右上角(c,d)两个点来表示:
cpp复制struct Carpet {
int a, b, c, d;
};
vector<Carpet> carpets;
2.2 输入处理
输入格式通常为:
- 第一行:n(地毯数量)
- 接下来n行:每行四个整数a,b,c,d表示一块地毯
- 最后一行:x,y表示查询点
读取输入的示例代码:
cpp复制int n;
cin >> n;
vector<Carpet> carpets(n);
for(int i=0; i<n; i++) {
cin >> carpets[i].a >> carpets[i].b >> carpets[i].c >> carpets[i].d;
}
int x, y;
cin >> x >> y;
2.3 查询实现
对于查询点(x,y),我们需要遍历所有地毯,检查是否被覆盖:
cpp复制int count = 0;
for(const auto& carpet : carpets) {
if(x >= carpet.a && x <= carpet.c &&
y >= carpet.b && y <= carpet.d) {
count++;
}
}
cout << count << endl;
2.4 完整代码示例
cpp复制#include <iostream>
#include <vector>
using namespace std;
struct Carpet {
int a, b, c, d;
};
int main() {
int n;
cin >> n;
vector<Carpet> carpets(n);
for(int i=0; i<n; i++) {
cin >> carpets[i].a >> carpets[i].b >> carpets[i].c >> carpets[i].d;
}
int x, y;
cin >> x >> y;
int count = 0;
for(const auto& carpet : carpets) {
if(x >= carpet.a && x <= carpet.c &&
y >= carpet.b && y <= carpet.d) {
count++;
}
}
cout << count << endl;
return 0;
}
3. 算法优化思路
虽然暴力解法在比赛环境下通常足够,但了解更高效的算法对提升编程能力很有帮助。下面介绍差分数组的优化方法。
3.1 差分数组原理
差分数组是一种高效处理区间更新的数据结构。对于二维情况,我们可以使用二维差分数组:
- 初始化一个(n+2)×(m+2)的差分数组diff(为避免边界检查)
- 对于每块地毯(a,b,c,d):
- diff[a][b] += 1
- diff[a][d+1] -= 1
- diff[c+1][b] -= 1
- diff[c+1][d+1] += 1
- 通过前缀和计算每个点的覆盖次数
3.2 差分数组实现
cpp复制const int MAX = 10010; // 根据题目调整
int diff[MAX][MAX] = {0};
// 铺设地毯
void addCarpet(int a, int b, int c, int d) {
diff[a][b]++;
diff[a][d+1]--;
diff[c+1][b]--;
diff[c+1][d+1]++;
}
// 计算前缀和
void computePrefix() {
for(int i=1; i<MAX; i++) {
for(int j=1; j<MAX; j++) {
diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1];
}
}
}
// 查询点(x,y)的覆盖次数
int query(int x, int y) {
return diff[x][y];
}
3.3 复杂度分析
- 预处理:O(k + nm)
- 查询:O(1)
- 空间:O(nm)
当n,m≤1e4时,这种方法非常高效。但对于更大的n,m(如1e5),内存可能不足。
4. 常见错误与调试技巧
4.1 边界条件处理
常见错误包括:
-
地毯坐标包含边界点
- 题目通常说明"左下角(a,b)和右上角(c,d)",意味着包含边界
- 比较时应使用x>=a && x<=c,而不是x>a && x<c
-
坐标从0还是1开始
- 题目可能说明"左下角(0,0)"或"(1,1)"
- 需要仔细阅读题目描述
4.2 性能优化
对于暴力解法,当k很大时(如1e5),查询可能超时。可以尝试:
- 提前终止:如果题目只需要判断是否被覆盖(而非计数),找到第一个覆盖的地毯后即可返回
- 输入优化:使用更快的输入方法(如scanf代替cin)
4.3 测试用例设计
建议设计以下测试用例:
- 单块地毯覆盖查询点
- 多块地毯重叠覆盖查询点
- 查询点在地毯边界上
- 查询点不被任何地毯覆盖
- 最大规模测试(如k=1e4)
示例测试用例:
code复制3
1 1 3 3
2 2 4 4
3 3 5 5
3 3
预期输出:3(被所有地毯覆盖)
5. 竞赛技巧与扩展思考
5.1 蓝桥杯答题技巧
-
仔细阅读输入输出格式
- 蓝桥杯通常严格要求输入输出格式
- 注意是否需要输出换行、空格等
-
合理选择算法
- 根据数据规模选择暴力或优化算法
- 先保证正确性,再考虑优化
-
调试方法
- 使用cout输出中间结果
- 设计小规模测试用例验证
5.2 问题扩展
-
如果查询多个点怎么办?
- 暴力解法:每个点O(k)
- 差分数组:预处理O(nm),每个查询O(1)
-
如果要求最高覆盖层数及其位置?
- 需要计算整个区域的覆盖情况
- 差分数组+遍历寻找最大值
-
如果地毯有透明度(部分覆盖)?
- 需要记录每点的覆盖情况
- 可能需要浮点数处理
5.3 类似题目推荐
- 矩形面积并(计算所有地毯覆盖的总面积)
- 矩形面积交(计算所有地毯共同覆盖的区域)
- 线段覆盖(一维版本的铺地毯问题)
6. 实际应用场景
这类算法在实际中有广泛应用:
- 图形处理:图层叠加计算
- 游戏开发:碰撞检测、区域覆盖判断
- GIS系统:地理区域覆盖分析
- 电子设计自动化:电路板布线检查
理解这类二维区间处理问题,对解决更复杂的空间计算问题很有帮助。
