瑞瑞的会议安排
【题目来源】
瑞学堂:瑞瑞的会议安排
【题目描述】
瑞瑞是一家科技公司的CEO,每天都要参加许多场会议。今天有 \(n\) 场会议,第 \(i\) 场会议的开始时间为 \(s_i\),结束时间为 \(e_i\)。
由于时间冲突,瑞瑞不可能参加所有的会议。他决定在一天中参加两段不相交的时间段,中间可以有一段休息时间。具体地,他可以选择两个时间段 \([L_1,R_1]\) 和 \([L_2,R_2]\),其中 \(R_1<L_2\),然后在第一个时间段内参加若干场会议(这些会议的开始时间和结束时间必须全部包含在 \([L_1,R_1]\) 内),在第二个时间段内同样参加若干场会议(全部包含在 \([L_2,R_2]\) 内),并且要求他参加的会议总数最多。
注意:
- 一场会议不能跨时间段参加。
- 在同一个时间段内,瑞瑞不能同时参加多场会议,即所选会议的时间(左闭右闭区间)不能有重叠。
- 会议的时间区间是左闭右闭区间 \([s_i,e_i]\)。
- 每个时间段内参加的会议数量可以为 \(0\)。
请你帮助瑞瑞算出他最多能参加多少场会议。
【输入】
第一行一个整数 \(n\),表示会议的数量。
接下来 \(n\) 行,每行两个整数 \(s_i,e_i\),表示第 \(i\) 场会议的开始和结束时间。
【输出】
输出一个整数,表示瑞瑞最多能参加的会议数量。
【输入样例】
4
1 3
2 4
5 7
6 8
【输出样例】
2
【核心思想】
-
问题分析:给定 \(n\) 场会议(时间区间 \([s_i, e_i]\)),瑞瑞可以参加两段不相交的时间段,每段内部不能同时参加重叠会议,求最多能参加的会议总数。这是一个双段区间调度问题,关键在于将会议按结束时间排序后,枚举两段的分界点,分别计算前缀和后缀的最大不重叠会议数。
-
算法选择:
- 贪心排序:按结束时间升序排序,这是经典区间调度问题的最优策略(优先选结束早的会议,留下更多时间给后续会议)
- 前缀/后缀预处理:计算 \(pre[i]\)(前 \(i\) 场会议中最多能选的不重叠会议数)和 \(suf[i]\)(后 \(i\) 场会议中最多能选的不重叠会议数)
- 枚举分界点:枚举第一段结束于第 \(i\) 场会议,第二段从第 \(i+1\) 场开始,答案为 \(\max(pre[i] + suf[i+1])\)
-
关键步骤:
- 读入与排序:读入 \(n\) 和会议数组 \(a[1..n]\),按结束时间 \(r\) 升序排序
- 预处理前缀 \(pre[i]\):\(pre[i]\) 表示前 \(i\) 场会议中最多能选的不重叠会议数
- 贪心遍历:维护
last(上一个已选会议的结束时间),若 \(a[i].l > last\) 则选中,pre[i] = pre[i-1] + 1,更新last = a[i].r;否则pre[i] = pre[i-1]
- 贪心遍历:维护
- 预处理后缀 \(suf[i]\):\(suf[i]\) 表示从第 \(i\) 场到第 \(n\) 场中最多能选的不重叠会议数
- 逆序贪心遍历:维护
first(下一个已选会议的开始时间),若 \(a[i].r < first\) 则选中,suf[i] = suf[i+1] + 1,更新first = a[i].l;否则suf[i] = suf[i+1]
- 逆序贪心遍历:维护
- 枚举两段分界:\(ans = \max_{i=0}^{n}(pre[i] + suf[i+1])\)(\(pre[0] = 0, suf[n+1] = 0\))
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),排序 \(O(n \log n)\),预处理 \(O(n)\),枚举 \(O(n)\)
- 空间复杂度:\(O(n)\),存储 \(pre\) 和 \(suf\) 数组
-
双段贪心调度的核心思想:
- 排序创造贪心最优性:按结束时间排序后,经典区间调度问题的贪心策略(选结束最早的兼容会议)可得到最优解
- 前缀/后缀分解:将"两段不相交"的约束转化为"在某处切分,左边一段最优 + 右边一段最优",避免直接处理两段交叉的复杂状态
- 贪心最优子结构:前缀和后缀各自独立使用贪心策略,由于排序后的全局最优性,分解后的局部最优之和即为全局最优
- 两段可为空的处理:\(pre[0] = 0\) 和 \(suf[n+1] = 0\) 自然覆盖只选一段或一段都不选的情况
- 适用于"将资源分为两段使用,每段内部有独立约束"的调度问题,核心在于排序后利用贪心最优性进行前后缀分解
【算法标签】
贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 2005; // 定义数组最大容量为2005
struct Node
{
int l, r; // l为会议开始时间,r为会议结束时间
} a[N]; // a数组存储n场会议的信息
int n, ans; // n为会议数量,ans记录最多能参加的会议数
// 自定义排序规则:按会议结束时间升序排列(贪心策略:优先选结束早的会议)
bool cmp(Node x, Node y)
{
return x.r < y.r; // 按结束时间从小到大排序
}
int main()
{
cin >> n; // 读入会议数量
for (int i = 1; i <= n; i++) // 读入每场会议的开始和结束时间
cin >> a[i].l >> a[i].r;
sort(a + 1, a + n + 1, cmp); // 按结束时间升序排序所有会议
// 贪心选择第一个时间段内的会议(经典区间调度问题)
int last = a[1].r; // last记录上一个已选会议的结束时间
ans = 1; // 第一个会议一定选
for (int i = 2; i <= n; i++) // 从第2个会议开始遍历
{
if (a[i].l <= last) // 如果当前会议开始时间小于等于上一个会议结束时间(有重叠)
continue; // 不能参加,跳过
ans++; // 可以参加,会议数加1
last = a[i].r; // 更新last为当前会议的结束时间
}
cout << ans << endl; // 输出最多能参加的会议数量(注意:此代码只处理了单个时间段,未处理两个时间段的情况)
return 0;
}
【运行结果】
4
1 3
2 4
5 7
6 8
2
浙公网安备 33010602011771号