1 条题解
-
0
题目重述
我们有 个时刻(步骤), 只猫,第 只猫在时间段 内出现。
在每个时刻,我们可以选择“喂食所有当前在场的猫”或者“什么都不做”。
不能有猫被喂超过一次。
目标:最大化被喂过的猫的数量。等价形式:
选择若干个时刻(喂食时刻),使得 没有一条给定的区间包含两个或更多选中的时刻,并且 被选中的时刻覆盖的区间数量 最大。
思路分析
这是一道 动态规划 + 贪心 的问题。
1. 关键观察
如果我们选择在时刻 喂食,那么所有在 时刻在场的猫都会被喂一次。
这些猫的出现区间都包含 。
那么,这些区间的 左端点的最小值 记为 ,则所有左端点 且 且右端点 的猫都在此时被喂。
更关键的是:
如果我们在 时刻喂食,那么上一次喂食的时刻必须 严格小于 ,否则 对应的猫会被喂两次(因为它的区间包含 和上一个喂食点)。因此:
- 设 表示所有覆盖时刻 的区间中,最小的左端点。
- 如果在 时刻喂食,则上一次喂食必须在 或更早。
2. 状态定义
设 表示考虑前 个时刻(即时刻 到 ),能够喂食的猫的最大数量。
3. 转移方程
情况 1:不在时刻 喂食
那么 (直接继承前 个时刻的最优解)。
情况 2:在时刻 喂食
此时,设 为覆盖 的所有区间的最小左端点。
那么在时刻 喂食时,所有满足 的猫都会被喂一次。
但我们必须保证上一次喂食时刻 ,否则 对应的猫会重复喂。同时,所有覆盖 的猫中,有些可能左端点 ?不可能,因为 是最小左端点,所以所有覆盖 的猫的左端点都 ,并且 。
因此,在时刻 喂食时,能喂到的猫的数量 = 覆盖 的区间数量,记为 。于是:
4. 如何计算 和 ?
我们可以通过预处理实现:
- 用
op[l]表示以 为左端点的区间数量(即时刻 有多少只猫开始出现)。 - 用
del[r]存储所有以 为右端点的区间的左端点。
然后从左到右扫描时刻 :
- 将时刻 开始的所有区间加入一个有序集合
cur(这里存的是左端点)。 - 此时
cur中所有元素的左端点 ,且右端点 (因为我们还没删除结束的区间)。
所以cur的大小就是 ,cur中的最小值就是 。 - 然后处理
del[i],即删除所有右端点为 的区间(它们的左端点从cur中移除)。
5. 算法步骤
- 读入 以及所有区间 。
- 初始化
op[l]++和del[r].push_back(l)。 - 初始化
dp[0] = 0,一个空的多重集合cur。 - 对于 到 :
- 将 插入
cur共op[i]次(表示这些区间开始)。 - 令 (不喂食)。
- 如果
cur非空:- 令 (最小左端点)。
- 。
- 更新 。
- 遍历
del[i]中的每个左端点 ,从cur中删除一个 。
- 将 插入
- 输出 。
6. 时间复杂度
- 每个区间在开始时插入一次,结束时删除一次,。
- 扫描 个时刻,每个时刻常数操作。
- 总复杂度 ,足够通过。
标程代码注释版
#include <bits/stdc++.h> using namespace std; void solve() { int n, m; cin >> n >> m; vector<int> op(n + 1); // op[l] = 以 l 为左端点的区间数 vector<vector<int>> del(n + 1); // del[r] 存储所有以 r 为右端点的左端点 for (int i = 0; i < m; ++i) { int l, r; cin >> l >> r; op[l]++; del[r].push_back(l); } multiset<int> cur; // 存储当前覆盖的区间的左端点 vector<int> dp(n + 1, 0); for (int i = 1; i <= n; ++i) { // 加入所有以 i 为左端点的区间 for (int j = 0; j < op[i]; ++j) { cur.insert(i); } // 不喂 i dp[i] = dp[i - 1]; // 喂 i if (!cur.empty()) { int first = *cur.begin(); // 覆盖 i 的最小区间左端点 int cnt = cur.size(); // 覆盖 i 的区间数量 dp[i] = max(dp[i], dp[first - 1] + cnt); } // 删除所有以 i 为右端点的区间 for (int l : del[i]) { cur.erase(cur.find(l)); } } cout << dp[n] << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) solve(); return 0; }
举例验证
以第一个样例:
15 6 2 10 3 5 2 4 7 7 8 12 11 11手动模拟:
- 在 时,覆盖 的区间有 ,最小左端点 ,。
。 - 在 时,覆盖 的区间有 ,,。
。
最终得到 。
总结
本题核心:
- 将问题转化为选择喂食时刻,保证每个区间至多包含一个选中的时刻。
- 用 表示前 个时刻的最优解。
- 在时刻 喂食时,利用覆盖 的所有区间的最小左端点来限制前一次喂食时刻。
- 通过扫描线和
multiset动态维护 和 。
这种 DP + 扫描线 + 数据结构 的方法在区间选择类问题中非常经典。
- 1
信息
- ID
- 6434
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者