一类线段与点覆盖问题的总结

· · 算法·理论

前言

感觉自己一直不太擅长这些问题,最近又遇到了好几道,像在提醒我有必要记一记了。

题目按照主算法分类,每类中的 题目按照我认为的 Codeforces 难度 Rating 排序。

所有题目都会上传到 Typical & Tricky Online Judge。

[持续更新中]

贪心

例 1

给定 n 条线段,每条线段覆盖一个闭区间 [l_i, r_i]。问最多选出多少条线段使得选出的任意两条线段都没有覆盖公共点。

### 解 1 将所有线段按右端点从小到大排序。记 $R$ 表示最后一条选择的线段的右端点,$ans $ 表示最终答案。初始 $R \gets 0, ans \gets 0$。从左往右依次考虑所有线段。若 $R < l_i$ 则执行 $R \gets r_i$,$ans \gets ans + 1$。 ### 解 1 的正确性证明(解 2) 用一个别解来证明。 同样先将所有线段按照右端点从小到大排序。 记 $f_i$ 为考虑完前 $i - 1$ 条线段,第 $i$ 条线段必须选时最多能选多少条不相交的线段。 初始时 $\forall i \in \mathbb{N} : 1 \leq i \leq n, f_i \gets 1$。 主动转移和被动转移皆可,为了最后的形式和解 1 类似,我们选择主动转移。 ```cpp for (int i = 1; i <= n; ++i) { for (int j = i + 1; j <= n; ++j) { if (r[i] < l[j]) { f[j] = max(f[j], f[i] + 1); } } } ``` 答案即为 $\max\limits_{1 \leq i \leq n}f_i$,这样实现时间复杂度为 $O(n^2)$。 事实上,每个 $f_i$ 转移给第一个 $r[i] < l[j]$ 的 $f_j$ 就已经足够了。因为 $r_i$ 单调不降,所以若 $i < k$,则 $f_i$ 能转移到的线段的集合一定是 $f_k$ 能转移到的线段的集合的超集。 有了这一事实,不难发现解 1 与解 2 形式上完全一致。 ### 难度 1600 ## 例 2