题目描述
国际保护与控制公司(ICPC\texttt{ICPC}ICPC )希望监控他们的总部大楼。大楼呈凸多边形,有 nnn 面墙(编号 111 到 nnn ),周围有 kkk 个可安装摄像头的位置。每个摄像头可以覆盖一段连续的墙壁。具体来说,对于第 iii 个摄像头,给定参数 aia_iai 和 bib_ibi:
- 若 ai≤bia_i \le b_iai≤bi ,则覆盖区间 [ai,bi][a_i, b_i][ai,bi] 。
- 若 ai>bia_i > b_iai>bi ,则覆盖区间 [ai,n]∪[1,bi][a_i, n] \cup [1, b_i][ai,n]∪[1,bi] (即跨越 nnn 到 111 的环形区间)。
两个摄像头的覆盖范围可以重叠。目标是选择最少的摄像头,使得所有墙壁都被覆盖。若无法覆盖所有墙壁,输出 impossible 。
输入格式 : 多组数据。每组第一行两个整数 nnn 和 kkk ( 3≤n≤1063 \le n \le 10^63≤n≤106 , 1≤k≤1061 \le k \le 10^61≤k≤106 ),随后 kkk 行每行两个整数 ai,bia_i, b_iai,bi 。
输出格式 : 每组数据输出最少摄像头数,或 impossible 。
题目分析与解题思路
1. 问题转化:环形转链
本题的核心是环形区间覆盖问题。墙壁编号 111 到 nnn 形成一个环,每个摄像头覆盖环上的一段连续墙壁。处理环形问题的常用技巧是破环成链:将长度为 nnn 的环复制一份,得到长度为 2n2n2n 的链。这样,环上任意一个跨越 nnn 到 111 的区间,在链上都可以表示为一个普通区间。
具体来说,对于每个摄像头给出的 (a,b)(a, b)(a,b) :
- 若 a≤ba \le ba≤b :在链上对应区间 [a,b][a, b][a,b] 和 [a+n,b+n][a+n, b+n][a+n,b+n] (复制部分)。
- 若 a>ba > ba>b :在环上覆盖 [a,n]∪[1,b][a, n] \cup [1, b][a,n]∪[1,b] ,等价于链上区间 [a,b+n][a, b+n][a,b+n] 。
注意,由于环上的一段连续墙壁长度不超过 nnn ,因此在链上我们只关心长度不超过 nnn 的区间。
2. 问题转化:最小区间覆盖
在 2n2n2n 的链上,我们需要覆盖任意一段长度为 nnn 的连续区间(例如 [1,n][1, n][1,n] ),这等价于覆盖整个环。问题转化为:在链上选择最少的区间,覆盖某个长度为 nnn 的连续段。
这是一个经典的最小区间覆盖问题,可以用贪心算法解决:
- 将区间按左端点排序。
- 设当前覆盖到位置 curcurcur ,初始为起点 startstartstart 。
- 在左端点 ≤cur\le cur≤cur 的所有区间中,选择右端点最大的一个,将 curcurcur 更新为该右端点,同时计数加一。
- 重复直到 cur≥start+ncur \ge start + ncur≥start+n ,表示已覆盖整个长度为 nnn 的段。
如果某一步无法找到左端点 ≤cur\le cur≤cur 的区间,则覆盖失败。
3. 算法优化:预处理与倍增
直接对每个起点 startstartstart ( 1≤start≤n1 \le start \le n1≤start≤n )进行贪心,时间复杂度为 O(n2)O(n^2)O(n2) ,无法通过 n≤106n \le 10^6n≤106 的数据。我们需要更高效的方法。
关键观察 :对于链上每个位置 iii ,我们可以预处理出从 iii 开始(选择左端点 ≤i\le i≤i 的区间)能够到达的最远位置,记为 next[i]next[i]next[i] 。这可以通过扫描所有区间并取前缀最大值得到。
有了 next[i]next[i]next[i] ,贪心过程可以描述为:从 startstartstart 出发,不断跳到 next[cur]next[cur]next[cur] ,直到越过 start+nstart+nstart+n 。这相当于在一条链上跳跃,每次跳到下一个最远可达位置。
为了快速计算从每个起点出发所需的跳跃次数,我们可以使用倍增法( Binary Lifting\texttt{Binary Lifting}Binary Lifting ):
- 令 dp[i][j]dp[i][j]dp[i][j] 表示从位置 iii 出发,经过 2j2^j2j 次跳跃后到达的位置。
- 初始 dp[i][0]=next[i]dp[i][0] = next[i]dp[i][0]=next[i] 。
- 转移: dp[i][j]=dp[dp[i][j−1]][j−1]dp[i][j] = dp[dp[i][j-1]][j-1]dp[i][j]=dp[dp[i][j−1]][j−1] 。
这样,对于每个起点 startstartstart ,我们从高位到低位尝试跳跃:如果从当前位置 curcurcur 跳 2j2^j2j 步后仍未到达 start+nstart+nstart+n ,则执行这次跳跃,并将步数加上 2j2^j2j 。最后再跳一步即可。
4. 算法步骤
- 读入与标准化 : 将每个区间 (a,b)(a, b)(a,b) 标准化为链上区间。注意,对于 a≤ba \le ba≤b 的情况,需要同时处理原区间和复制区间。
- 预处理 nextnextnext 数组 :
- 初始化 next[i]=inext[i] = inext[i]=i 。
- 对于每个标准化后的区间 [l,r][l, r][l,r] ,更新 next[l]=max(next[l],r)next[l] = \max(next[l], r)next[l]=max(next[l],r) 。
- 计算前缀最大值: next[i]=max(next[i],next[i−1])next[i] = \max(next[i], next[i-1])next[i]=max(next[i],next[i−1]) 。
- 构建倍增表 :
- dp[i][0]=next[i]+1dp[i][0] = next[i] + 1dp[i][0]=next[i]+1 (加 111 是为了跳到下一个未覆盖的位置)。
- 对于 j≥1j \ge 1j≥1 , dp[i][j]=dp[dp[i][j−1]][j−1]dp[i][j] = dp[dp[i][j-1]][j-1]dp[i][j]=dp[dp[i][j−1]][j−1] 。
- 注意处理边界,超出 2n2n2n 的位置设为 2n+12n+12n+1 。
- 枚举起点并计算 :
- 对每个起点 start=1,2,…,nstart = 1, 2, \dots, nstart=1,2,…,n :
- 用倍增法计算从 startstartstart 出发,需要多少步(即多少个区间)才能覆盖 [start,start+n−1][start, start+n-1][start,start+n−1] 。
- 记录最小步数。
- 对每个起点 start=1,2,…,nstart = 1, 2, \dots, nstart=1,2,…,n :
- 输出答案 : 若最小步数不超过 kkk ,输出该步数;否则输出
impossible。
5. 时间复杂度与空间复杂度
- 预处理 nextnextnext 数组: O(n+k)O(n + k)O(n+k) 。
- 构建倍增表: O(nlogn)O(n \log n)O(nlogn) 。
- 枚举起点并查询: O(nlogn)O(n \log n)O(nlogn) 。
- 总时间复杂度: O((n+k)logn)O((n+k) \log n)O((n+k)logn) ,满足 n,k≤106n, k \le 10^6n,k≤106 的限制。
- 空间复杂度: O(nlogn)O(n \log n)O(nlogn) ,主要来自倍增表。
代码实现
// Surveillance
// UVa ID: 1707
// Verdict: Accepted
// Submission Date: 2025-12-05
// UVa Run Time: 2.800s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e6 + 10; // 注意:2 * n 可能达到 2e6
const int LOG = 22; // 2^21 > 2e6
int n, k;
int maxReach[2 * MAXN]; // maxReach[i] = 从 ≤ i 的位置开始能覆盖到的最远位置
int dp[2 * MAXN][LOG]; // 倍增表
int main() {
while (scanf("%d %d", &n, &k) == 2) {
// 初始化
for (int i = 1; i <= 2 * n; ++i)
maxReach[i] = i;
// 处理每个摄像头
for (int i = 0; i < k; ++i) {
int a, b;
scanf("%d %d", &a, &b);
if (a <= b) {
// 区间 [a, b]
maxReach[a] = max(maxReach[a], b);
// 在链的复制部分也要更新
if (a + n <= 2 * n)
maxReach[a + n] = max(maxReach[a + n], b + n);
} else {
// 区间 [a, b+n](在环上覆盖了a到n和1到b)
maxReach[a] = max(maxReach[a], b + n);
}
}
// 计算前缀最大值
for (int i = 2; i <= 2 * n; ++i)
maxReach[i] = max(maxReach[i], maxReach[i - 1]);
// 构建倍增表
for (int i = 1; i <= 2 * n; ++i)
dp[i][0] = maxReach[i] + 1; // 注意:+1是为了方便,表示选择这个区间后能到达的下一个位置
// 如果dp[i][0]超过边界,设为2*n+1
for (int i = 1; i <= 2 * n; ++i)
if (dp[i][0] > 2 * n) dp[i][0] = 2 * n + 1;
// 构建倍增表
for (int j = 1; j < LOG; ++j)
for (int i = 1; i <= 2 * n; ++i)
if (dp[i][j - 1] <= 2 * n)
dp[i][j] = dp[dp[i][j - 1]][j - 1];
else
dp[i][j] = 2 * n + 1;
int ans = k + 1;
// 检查每个起点
for (int start = 1; start <= n; ++start) {
int current = start;
int steps = 0;
// 使用倍增找到覆盖[start, start+n-1]所需的最小步数
for (int j = LOG - 1; j >= 0; --j) {
if (dp[current][j] < start + n) {
current = dp[current][j];
steps += (1 << j);
}
}
// 再走一步
current = dp[current][0];
steps++;
if (current <= start + n) {
ans = min(ans, steps);
}
}
if (ans > k)
printf("impossible\n");
else
printf("%d\n", ans);
}
return 0;
}
总结
本题是环形区间覆盖问题的经典例题,通过破环成链技巧将环形问题转化为线性问题,再使用贪心+倍增的方法高效求解。关键在于预处理每个位置能到达的最远位置,并利用倍增表快速模拟贪心过程。算法的时间复杂度为 O((n+k)logn)O((n+k) \log n)O((n+k)logn) ,能够处理 n,kn, kn,k 高达 10610^6106 的大规模数据。
这类问题在竞赛中常见,掌握破环成链和倍增技巧对于解决类似问题非常有帮助。
3049

被折叠的 条评论
为什么被折叠?



