题目描述
海伦是一个多动症的女孩。她想要安排自己的活动,使得一天中的任何一个时刻,至少有一件事可以做。她不在乎活动在时间上是否重叠,只要一天中的每一时刻都有活动安排即可。
海伦把一天做了特殊划分:一天从时刻 0 0 0 开始,到时刻 M M M 结束。一天中的每个时刻用 0 0 0 到 M M M 之间的一个实数表示(包含端点)。海伦列出了所有可能的活动,每个活动有开始和结束时间。现在她需要选择要安排的活动子集。
如果一个活动从时刻 S S S 开始,到时刻 F F F 结束,我们就说它覆盖了从 S S S 到 F F F 之间(包含端点)的所有时刻。海伦不想浪费任何活动,所以她只会选择能覆盖全天的最小子集来安排。一个活动子集是能覆盖全天的最小子集,当且仅当:
- 一天中的每个时刻至少被子集中的某个活动覆盖;
- 从该子集中移除任意一个活动,都会导致至少有一个时刻未被覆盖。
注意:某些时刻可能被多个活动同时覆盖。
给定一天中所有可能的活动列表,你需要帮助海伦计算有多少个不同的最小覆盖子集。
输入格式
每个测试用例由若干行给出。第一行包含两个整数 M M M 和 N N N,分别表示一天中时刻的最大值( 1 ≤ M ≤ 10 9 1 \le M \le 10^9 1≤M≤109)和活动数量( 1 ≤ N ≤ 100 1 \le N \le 100 1≤N≤100)。接下来 N N N 行,每行描述一个活动,包含两个整数 S S S 和 F F F,表示活动的开始和结束时间( 0 ≤ S < F ≤ M 0 \le S < F \le M 0≤S<F≤M)。
最后一个测试用例后跟一行包含两个 0 0 0。
输出格式
对于每个测试用例,输出一行一个整数,表示能覆盖全天的最小子集的数量。为了简化,输出结果对 10 8 10^8 108 取模。
样例
输入
8 7
0 3
2 5
5 8
1 3
3 6
4 6
0 2
1 1
0 1
2 1
0 1
0 0
输出
4
1
0
题目分析
本题要求统计所有能够覆盖区间 [ 0 , M ] [0, M] [0,M] 的极小活动子集的数量。所谓极小,是指该子集满足覆盖条件,但移除其中任何一个活动都会导致覆盖失败。
直接枚举所有子集显然不可行,因为 N N N 最大为 100 100 100,子集数量为 2 100 2^{100} 2100。我们需要利用区间覆盖的结构特性来设计动态规划算法。
首先,区间覆盖具有天然的序关系。如果一个活动区间 A A A 的右端点小于另一个活动区间 B B B 的左端点,那么 A A A 无法直接接续到 B B B,中间存在空隙。反之,如果 A A A 的右端点大于等于 B B B 的左端点,则 A A A 和 B B B 可以拼接成更大的覆盖范围。
在一个极小覆盖中,活动区间按照某种顺序排列后,每个区间都必须贡献新的覆盖范围。这意味着区间的右端点必须严格递增(否则后一个区间没有扩展覆盖范围,可以被移除),同时区间的左端点也必须严格递增(否则后一个区间完全被前一个区间覆盖了起始部分,也可能被替代)。更重要的是,对于任意三个连续的区间 i → j → k i \to j \to k i→j→k,必须满足 F i < S k \textit{F}_i < \textit{S}_k Fi<Sk,否则区间 j j j 可以被区间 i i i 和区间 k k k 的组合替代,从而 j j j 不是必要的。
因此,极小覆盖可以看作是在区间集合中寻找满足上述严格条件的有向路径,从某个左端点为 0 0 0 的区间出发,到达某个右端点大于等于 M M M 的区间。
解题思路
转化为图论问题
将每个活动区间视为图中的一个顶点。如果区间 i i i 可以接续到区间 j j j(即 F i ≥ S j \textit{F}_i \ge \textit{S}_j Fi≥Sj),则从 i i i 到 j j j 连一条有向边。在此基础上,我们只保留满足以下条件的边:
- S i < S j \textit{S}_i < \textit{S}_j Si<Sj(左端点严格递增)
- F i < F j \textit{F}_i < \textit{F}_j Fi<Fj(右端点严格递增)
- 对于路径中的中间区间 j j j,还需要 F i < S k \textit{F}_i < \textit{S}_k Fi<Sk,其中 i i i 是 j j j 的前驱, k k k 是 j j j 的后继
条件 1 和 2 保证了每个区间都扩展了覆盖范围。条件 3 保证了中间区间 j j j 是必要的:如果 F i ≥ S k \textit{F}_i \ge \textit{S}_k Fi≥Sk,那么区间 j j j 被移除后,前驱区间 i i i 和后继区间 k k k 可以直接衔接,中间没有空隙,所以 j j j 不是必要的。
动态规划设计
由于 N ≤ 100 N \le 100 N≤100,我们可以使用二维动态规划。定义 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 表示以区间 i i i 和 j j j 作为路径中最后两个区间的极小覆盖路径数量,其中 i i i 是 j j j 的前驱。
初始化:对于任意两个区间 i i i 和 j j j,如果 S i = 0 \textit{S}_i = 0 Si=0(区间 i i i 从时刻 0 0 0 开始),且满足 S i < S j \textit{S}_i < \textit{S}_j Si<Sj、 F i ≥ S j \textit{F}_i \ge \textit{S}_j Fi≥Sj、 F i < F j \textit{F}_i < \textit{F}_j Fi<Fj,则 dp [ i ] [ j ] = 1 \textit{dp}[i][j] = 1 dp[i][j]=1。这表示只有两个区间的路径。
转移:从状态 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 转移到 dp [ j ] [ k ] \textit{dp}[j][k] dp[j][k],其中 k k k 是新加入的区间。需要满足以下条件:
- S j < S k \textit{S}_j < \textit{S}_k Sj<Sk(左端点严格递增)
- F j ≥ S k \textit{F}_j \ge \textit{S}_k Fj≥Sk(能接续上)
- F j < F k \textit{F}_j < \textit{F}_k Fj<Fk(右端点严格递增)
- F i < S k \textit{F}_i < \textit{S}_k Fi<Sk(区间 j j j 必要,不能被 i i i 和 k k k 替代)
转移方程为:
dp [ j ] [ k ] = ∑ i dp [ i ] [ j ] \textit{dp}[j][k] = \sum_{i} \textit{dp}[i][j] dp[j][k]=i∑dp[i][j]
其中 i i i 满足上述条件。
答案统计:所有满足 F j ≥ M \textit{F}_j \ge M Fj≥M 的状态 dp [ i ] [ j ] \textit{dp}[i][j] dp[i][j] 累加,再加上单个区间就能覆盖全天的方案(即 S i = 0 \textit{S}_i = 0 Si=0 且 F i ≥ M \textit{F}_i \ge M Fi≥M)。
为什么这样设计能正确计数
以样例中的第一个测试用例为例,区间列表为:
[
0
,
3
]
,
[
2
,
5
]
,
[
5
,
8
]
,
[
1
,
3
]
,
[
3
,
6
]
,
[
4
,
6
]
,
[
0
,
2
]
[0,3], [2,5], [5,8], [1,3], [3,6], [4,6], [0,2]
[0,3],[2,5],[5,8],[1,3],[3,6],[4,6],[0,2],
M
=
8
M = 8
M=8。
极小覆盖共有 4 个:
- [ 0 , 3 ] → [ 3 , 6 ] → [ 5 , 8 ] [0,3] \to [3,6] \to [5,8] [0,3]→[3,6]→[5,8]
- [ 0 , 3 ] → [ 2 , 5 ] → [ 5 , 8 ] [0,3] \to [2,5] \to [5,8] [0,3]→[2,5]→[5,8]
- [ 0 , 2 ] → [ 2 , 5 ] → [ 5 , 8 ] [0,2] \to [2,5] \to [5,8] [0,2]→[2,5]→[5,8]
- [ 0 , 2 ] → [ 1 , 3 ] → [ 3 , 6 ] → [ 5 , 8 ] [0,2] \to [1,3] \to [3,6] \to [5,8] [0,2]→[1,3]→[3,6]→[5,8]
我们的 DP 能够正确计数所有这些路径,同时排除非极小路径(例如 [ 0 , 3 ] → [ 2 , 5 ] → [ 3 , 6 ] → [ 5 , 8 ] [0,3] \to [2,5] \to [3,6] \to [5,8] [0,3]→[2,5]→[3,6]→[5,8],因为移除 [ 3 , 6 ] [3,6] [3,6] 后仍然覆盖全天)。
复杂度分析
- 排序区间: O ( N log N ) O(N \log N) O(NlogN)
- 动态规划:状态数为 O ( N 2 ) O(N^2) O(N2),每个状态转移需要 O ( N ) O(N) O(N) 枚举前驱,总时间复杂度 O ( N 3 ) O(N^3) O(N3)
- N ≤ 100 N \le 100 N≤100, O ( N 3 ) = 10 6 O(N^3) = 10^6 O(N3)=106,完全可以接受
- 空间复杂度: O ( N 2 ) O(N^2) O(N2)
代码实现
// Hyperactive Girl
// UVa ID: 12245
// Verdict: Accepted
// Submission Date: 2026-06-17
// UVa Run Time: 0.040s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int MOD = 100000000;
struct Interval {
int s, f;
};
int main() {
int M, N;
while (cin >> M >> N && (M != 0 || N != 0)) {
vector<Interval> acts(N);
for (int i = 0; i < N; ++i)
cin >> acts[i].s >> acts[i].f;
// 按左端点排序,便于保证左端点严格递增的条件
sort(acts.begin(), acts.end(), [](const Interval& a, const Interval& b) {
if (a.s != b.s) return a.s < b.s;
return a.f < b.f;
});
// dp[i][j]:以 i 和 j 为最后两个区间的极小覆盖路径数
vector<vector<int>> dp(N, vector<int>(N, 0));
int r = 0;
// 初始化:只有两个区间的路径
for (int i = 0; i < N; ++i) {
if (acts[i].s != 0) continue; // 第一个区间必须从0开始
for (int j = 0; j < N; ++j) {
if (i == j) continue;
if (acts[i].s < acts[j].s && // 左端点严格递增
acts[i].f >= acts[j].s && // 能接上
acts[i].f < acts[j].f) { // 右端点严格递增
dp[i][j] = 1;
}
}
}
// DP 转移
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
if (dp[i][j] == 0) continue;
for (int k = 0; k < N; ++k) {
if (k == i || k == j) continue;
// 左端点严格递增
if (acts[j].s >= acts[k].s) continue;
// 能接上
if (acts[j].f < acts[k].s) continue;
// 右端点严格递增
if (acts[j].f >= acts[k].f) continue;
// 极小性条件:区间 j 必须必要,即 F[i] < S[k]
if (acts[i].f >= acts[k].s) continue;
dp[j][k] += dp[i][j];
if (dp[j][k] >= MOD) dp[j][k] -= MOD;
}
}
}
// 统计答案:所有 F[j] >= M 的路径
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
if (acts[j].f >= M) {
r += dp[i][j];
r %= MOD;
}
}
}
// 特殊情况:单个区间就能覆盖 [0, M]
for (int i = 0; i < N; ++i) {
if (acts[i].s == 0 && acts[i].f >= M) {
r += 1;
r %= MOD;
}
}
cout << r % MOD << endl;
}
return 0;
}
总结
本题的核心是将区间覆盖问题转化为图上的路径计数问题,通过动态规划统计所有极小覆盖的数量。解题的关键点在于:
-
识别极小覆盖的结构特征:左端点严格递增、右端点严格递增,以及中间区间的不可替代性条件 F i < S k \textit{F}_{i} < \textit{S}_{k} Fi<Sk。
-
二维 DP 状态设计:由于需要检查中间区间的必要性,必须记录路径中最后两个区间,从而在加入新区间时验证前驱区间是否可被替代。
-
边界的正确处理:以左端点为 0 0 0 的区间作为起点,以右端点大于等于 M M M 的区间作为终点,并单独处理单区间覆盖的情况。
-
取模处理:答案对 10 8 10^8 108 取模,注意在累加过程中及时取模防止溢出。
该算法的时间复杂度为 O ( N 3 ) O(N^3) O(N3),在 N ≤ 100 N \le 100 N≤100 的限制下运行效率足够。本题展示了如何从区间覆盖的几何性质出发,设计合适的图模型和动态规划算法来解决计数问题,是一道典型的区间 DP 与图论结合的好题。

216

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



