题解 CF1215F 【Radio Stations】

Sooke

2019-09-16 11:45:20

Solution

### 简要题意 有 $n$ 个电台,第 $i$ 个电台频段在 $[l_i,\,r_i]$ 。由你在 $[1,\,m]$ 中选择主频 $f$ ,如果 $f \in [l_i,\,r_i]$ ,第 $i$ 个电台你可选择是否启用,否则无法启用。 有若干组限制形如 $u$ 和 $v$ 必须启用至少一个,或不可同时启用。请给出 $f$ 并构造电台的启用方案。无解输出 $-1$ 。 --- ### 前置知识 $\text{2 - SAT}$ 。[模板题链接](https://www.luogu.org/problem/P4782) 。至于这道题,虽然明显地指示出了这类问题,但是建模是前所未有的神仙! --- ### 解题思路 按照 $\text{2 - SAT}$ 的套路,我们把一个对象拆成两个点分别表示选和不选。 于是我们掏出 $2n$ 个点,来描述 $n$ 个电台的启用方案。 但最重要的考点,还是建模。 下文用 $\text{yes}(u)$ 表示表示对象 $u$ 选的点,$\text{no}(u)$ 表示对象 $u$ 不选的点。 首先来热一热身,“若干组限制形如电台 $u$ 和电台 $v$ 必须启用至少一个”怎么在 $\text{2 - SAT}$ 中表示呢? 回忆一下,既然点表示选或不选,那么条件 $u$ 往条件 $v$ 连一条有向边表示只要条件 $u$ 满足,条件 $v$ 就必须满足。 因此,我们只要像下面一样连边: ![](https://i.loli.net/2019/09/16/6wek2Vd3sMmDFJH.png) 即:$\text{no}(u) \rightarrow \text{yes}(v),\ \text{no}(v) \rightarrow \text{yes}(u)$ 。 理由很简单。当 $u$ 不选时,如果 $v$ 还不选的话,就不满足限制了,这意味着 $v$ 不得不选。反过来亦然。 下一个热身任务:若干组限制形如电台 $u$ 和电台 $v$ 不可同时启用。 连边方式异曲同工: ![](https://i.loli.net/2019/09/16/vR9NFuSekpByOAd.png) 即:$\text{yes}(u) \rightarrow \text{no}(v),\ \text{yes}(v) \rightarrow \text{no}(u)$ 。 理由也类似。当 $u$ 选,$v$ 必须不能选,否则违反限制。反过来亦然。 热身结束。真正振奋人心的建模这才到来:如何制定唯一的 $f$ ,并表示出因为 $f \notin [l_i,\,r_i]$ ,$i$ 号电台不允许启用? 线段树优化建边?太慢!太繁琐!我们需要**前缀和的思想**!试试把区间 $[l,\,r]$ 拆成 $[1,\,l-1]$ 和 $[1,\,r]$ 来讨论。 列出以下表格: |||| |:-:|:-:|:-:| |$\quad f\in [1,\,l_i - 1]\quad $|$\quad f\in [1,\,r_i]\quad $|$\quad $电台 $i$ 能否启用$\quad $| |$\text{no}$|$\text{no}$|$\text{no}$ ,此时 $f > r_i$| |$\text{no}$|$\text{yes}$|没有限制,此时 $f \in [l_i,\,r_i]$| |$\text{yes}$|$\text{yes}$|$\text{no}$ ,此时 $f < l_i$| |$\text{yes}$|$\text{no}$|不存在 ,$f \in \varnothing$| 据此,提取出三个重要限制: - 若电台 $i$ 启用,$f \in [l_i,\,r_i]$ 。 - 若 $f \in [1,\,l_i-1]$ 满足,电台 $i$ 无法启用。 - 若 $f \in [1,\,r_i]$ 不满足,电台 $i$ 无法启用。 与此同时,我们发现仅凭目前的 $2n$ 个点,不足以表示出上面的限制。 于是我们再掏出 $2(m+1)$ 个点,分别表示对于 $i = 0 \dots m$ ,$[f \le i]$ 满足或不满足。例如,用 $\text{yes}(n + i + 1)$ 表示 $f \le i$ ,用 $\text{no}(n + i + 1)$ 表示 $f > i$ 。 它们之间的连边如下: ![](https://i.loli.net/2019/09/16/b4kYHJQa1t2938n.png) 即:$\text{yes}(n+i+1) \rightarrow \text{yes}(n+i+2),\ \text{no}(n+i+2) \rightarrow \text{no}(n+i+1)$ 。 解释:$f \le i$ 满足时,$f \le i + 1$ 必然满足。$f \le i + 1$ 不满足时,即 $f \ge i + 1$ 满足时,$f \le i$ 必然不满足。 至此,我们终于准备在两类对象之间连边了! 复读一遍那三个限制: - 若电台 $i$ 启用,$f \in [l_i,\,r_i]$ 。 - 若 $f \in [1,\,l_i-1]$ 满足,电台 $i$ 无法启用。 - 若 $f \in [1,\,r_i]$ 不满足,电台 $i$ 无法启用。 转换成连边关系就是: - $\text{yes}(i) \rightarrow \text{no}(n+l_i),\ \text{yes}(i) \rightarrow \text{yes}(n+r_i+1)$ - $\text{yes}(n+l_i) \rightarrow \text{no}(i)$ - $\text{no}(n+r_i+1) \rightarrow \text{no}(i)$ ![](https://i.loli.net/2019/09/16/IYdKv7AZx3TyRoC.png) 还有一个小细节,$f$ 不能等于 $0$ !有个巧妙的连边是 $\text{yes}(n+1) \rightarrow \text{no}(n+1)$ ,这样 $\text{yes}(n+1)$ 成立时会造成矛盾,$\text{no}(n+1)$ 成立时刚好不会有问题。 建完图后,跑一跑 $\text{2 - SAT}$ 就行啦!判断无解的方法跟模板题一样,而有解时的 $f$ ,满足 $\text{yes}(n+f+1)$ ,$\text{no}(n+f)$ 成立 ,跑完 $\text{2 - SAT}$ 后枚举找到这个分界点即可。 --- ### 代码实现 注意我的代码实现里 $n$ 个电台是从 $0$ 到 $n - 1$ 标号的。 ```cpp #include <bits/stdc++.h> inline int read() { char c; int x; for (c = getchar(); !isdigit(c); c = getchar()); for (x = 0; isdigit(c); c = getchar()) { x = x * 10 + c - '0'; } return x; } const int N = 2e6 + 5, E = 4e6 + 5; struct List { int tot, fst[N], nxt[E], to[E]; List() { memset(fst, -1, sizeof(fst)); } inline void insert(int u, int v) { nxt[tot] = fst[u]; to[tot] = v; fst[u] = tot++; } inline void link(int u, int v) { insert(u, v); insert(v ^ 1, u ^ 1); } } e; int n, m, m0, m1, k, c, tms, vol, dfn[N], low[N], stc[N], col[N]; bool ins[N]; inline int yes(int u) { return u << 1; } inline int no(int u) { return u << 1 | 1; } void tarjan(int u) { dfn[u] = low[u] = tms++; int pos = vol; stc[vol++] = u; ins[u] = true; for (int i = e.fst[u]; ~i; i = e.nxt[i]) { int v = e.to[i]; if (dfn[v] == -1) { tarjan(v); low[u] = std::min(low[u], low[v]); } else if (ins[v]) { low[u] = std::min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { while (vol > pos) { int v = stc[--vol]; col[v] = c; ins[v] = false; } c++; } } bool solve() { for (int i = 0; i <= no(n + m); i++) { dfn[i] = -1; } for (int i = 0; i <= no(n + m); i++) { if (dfn[i] == -1) { tarjan(i); } } for (int i = 0; i <= n + m; i++) { if (col[yes(i)] == col[no(i)]) { return false; } } return true; } int main() { m0 = read(); n = read(); m = read(); m1 = read(); for (int i = 0; i < m0; i++) { int u = read(), v = read(); u--; v--; e.link(no(u), yes(v)); } for (int i = 0; i < m; i++) { e.link(yes(n + i), yes(n + i + 1)); } e.link(yes(n), no(n)); for (int i = 0; i < n; i++) { int l = read(), r = read(); e.link(yes(i), no(n + l - 1)); e.link(yes(i), yes(n + r)); } for (int i = 0; i < m1; i++) { int u = read(), v = read(); u--; v--; e.link(yes(u), no(v)); } if (solve()) { for (int i = 0; i < n; i++) { if (col[yes(i)] < col[no(i)]) { k++; } } for (int i = 1; i <= m; i++) { if (col[yes(n + i)] < col[no(n + i)]) { printf("%d %d\n", k, i); break; } } for (int i = 0; i < n; i++) { if (col[yes(i)] < col[no(i)]) { printf("%d ", i + 1); } } } else { puts("-1"); } return 0; } ```