题解:AtCoder AT_awc0006_d Placement of Security Guards
【题目来源】
AtCoder:D - Placement of Security Guards
【题目描述】
Takahashi is creating a security plan for a long corridor consisting of \(N\) sections (from section \(1\) to section \(N\)) arranged in a straight line. He must ensure that all sections are covered by security.
高桥正在为一条由 \(N\) 个区段(从区段 \(1\) 到区段 \(N\))组成的笔直长走廊制定安保计划。他必须确保所有区段都受到安保覆盖。
Takahashi has gathered \(M\) guard candidates. Each guard candidate \(i\) (\(1 \leq i \leq M\)) can cover a contiguous range from section \(L_i\) to section \(R_i\). That is, if guard candidate \(i\) is assigned, sections \(L_i, L_i + 1, \ldots, R_i\) are all covered.
高桥已招募了 \(M\) 名警卫候选人。每名警卫候选人 \(i\)(\(1 ≤ i ≤ M\))可以覆盖从区段 \(L_i\) 到区段 \(R_i\) 的连续范围。也就是说,如果警卫候选人 \(i\) 被录用,则区段 \(L_i, L_i+1, …, R_i\) 都将被覆盖。
Takahashi wants all \(N\) sections to be covered. Specifically, he wants to select some of the \(M\) guard candidates such that the union of the sections covered by the selected guards equals \(\{1, 2, \ldots, N\}\). Each guard candidate can either be selected or not selected; the same candidate cannot be selected more than once.
高桥希望所有 \(N\) 个区段都被覆盖。具体而言,他希望从 \(M\) 名警卫候选人中选择一部分,使得被录用警卫所覆盖区段的并集等于 \(\{1, 2, \ldots, N\}\)。每名警卫候选人要么被录用要么不被录用;同一候选人不能被录用超过一次。
Find the minimum number of guards that need to be assigned. If it is impossible to cover all sections regardless of how the guards are selected, output \(-1\).
求需要录用的最少警卫人数。如果无论怎样选择警卫都无法覆盖所有区段,则输出 \(-1\)。
【输入】
\(N\) \(M\)
\(L_1\) \(R_1\)
\(L_2\) \(R_2\)
\(\vdots\)
\(L_M\) \(R_M\)
- The first line contains an integer \(N\) representing the number of sections and an integer \(M\) representing the number of guard candidates, separated by a space.
- From the 2nd line to the \((M + 1)\)-th line, the range of sections each guard candidate can cover is given.
- The \((i + 1)\)-th line (\(1 \leq i \leq M\)) contains the left endpoint \(L_i\) and right endpoint \(R_i\) of the range of sections that guard candidate \(i\) can cover, separated by a space.
【输出】
Output in a single line the minimum number of guards needed to cover all sections. If it is impossible, output \(-1\).
【输入样例】
10 3
1 5
3 7
6 10
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 个区段和 \(M\) 名警卫候选人,每名警卫 \(i\) 可以覆盖区间 \([L_i, R_i]\)。需要选择最少数量的警卫,使得它们的覆盖区间的并集完全覆盖 \([1, N]\)。这是一个经典的区间覆盖问题,是贪心算法的典型应用。
-
算法选择:
- 贪心策略(Greedy):每次在能衔接当前覆盖范围的区间中,选择右端点最远的区间
- 按左端点排序:将所有区间按照 \(L_i\) 从小到大排序,左端点相同时按 \(R_i\) 排序
- 双指针/扫描线:维护当前已覆盖的最右位置 \(r\),在左端点不超过 \(r+1\) 的区间中选择右端点最大的
-
关键步骤:
- 读取输入:\(N\)(目标覆盖长度)、\(M\)(区间数量)、\(M\) 个区间 \([L_i, R_i]\)
- 按左端点排序:使用自定义比较函数按 \(L_i\) 升序,\(L_i\) 相同时按 \(R_i\) 升序
- 初始化:
r = 0(当前已覆盖的最右位置),cnt = 0(使用的区间数量) - 贪心覆盖(遍历区间,直到 \(r \geq N\)):
- 断层检查:如果当前区间的左端点 \(L_i > r + 1\),说明中间有覆盖不到的位置,输出 \(-1\)
- 选择最优区间:在所有左端点 \(L_j \leq r + 1\) 的区间中,找到右端点 \(R_j\) 最大的区间
- 无法扩展检查:如果最大右端点 \(\leq r\),说明无法继续扩展覆盖范围,输出 \(-1\)
- 更新覆盖:
r = maxr,cnt++,跳转到最优区间的位置继续
- 输出结果:如果 \(r \geq N\),输出 \(cnt\);否则输出 \(-1\)
-
时间/空间复杂度:
- 时间复杂度:\(O(M \log M)\),排序 \(O(M \log M)\),贪心扫描 \(O(M)\)(每个区间最多被访问一次)
- 空间复杂度:\(O(M)\),存储区间信息
-
区间覆盖问题的核心思想:
- 贪心正确性:每次选择能扩展最远覆盖范围的区间,确保用最少的区间覆盖最大的范围
- 排序预处理:按左端点排序使得可以顺序处理,左端点不超过当前覆盖范围的区间都在前面
- 双指针技巧:外层指针遍历区间,内层指针在所有可选区间中寻找右端点最大的
- 断层检测:如果当前可选区间的左端点都大于 \(r+1\),说明有间隙无法覆盖
- 适用场景:线段覆盖、区间选点、最少区间覆盖等问题
【解题思路】

【算法标签】
贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m, cnt; // n: 目标覆盖长度,m: 区间数量,cnt: 使用的区间数量
struct Node
{
int l, r; // 区间左右端点
}a[N];
// 比较函数:先按左端点升序,再按右端点升序
bool cmp(Node x, Node y)
{
if (x.l != y.l)
{
return x.l < y.l;
}
return x.r < y.r;
}
int main()
{
cin >> n >> m; // 读入目标长度和区间数量
for (int i = 1; i <= m; i++)
{
cin >> a[i].l >> a[i].r; // 读入区间
}
sort(a + 1, a + m + 1, cmp); // 将区间按左端点排序
int r = 0; // 当前已覆盖到的最右位置,初始为0
for (int i = 1; i <= m && r < n; i++) // 遍历区间,直到覆盖到n
{
// 如果当前区间的左端点大于r+1,说明有覆盖不到的位置
if (a[i].l > r + 1)
{
cout << -1 << endl;
return 0;
}
int maxr = 0, maxi = i; // 记录可选的区间中最大的右端点和对应的下标
// 找到所有左端点不超过r+1的区间中,右端点最大的那个
for (int j = i; j <= m && a[j].l <= r + 1; j++)
{
if (a[j].r > maxr)
{
maxr = a[j].r;
maxi = j;
}
}
// 如果没有能扩展覆盖范围的区间
if (maxr <= r)
{
cout << -1 << endl;
return 0;
}
r = maxr; // 更新已覆盖的最右位置
cnt++; // 使用的区间数加1
i = maxi; // 跳转到最大右端点对应的区间
}
// 判断是否覆盖到目标长度n
if (r >= n)
{
cout << cnt;
}
else
{
cout << -1 << endl;
}
return 0;
}
【运行结果】
10 3
1 5
3 7
6 10
2
浙公网安备 33010602011771号