建图后我们使用 Tarjan 算法找 SCC,判断对于任意布尔变量 a,表示 a 成立的点和表示 a 不成立的点是否在同一个 SCC 中(同一条件不可能既满足又不满足,或既不满足又并非不满足),若有则输出无解,否则有解。
输出方案时可以通过变量在图中的拓扑序确定该变量的取值。如果变量 x 的拓扑序在 ¬x 之后,那么取 x 值为真。应用到 Tarjan 算法的缩点,即 x 所在 SCC 编号在 ¬x 之前时,取 x 为真。因为 Tarjan 算法求强连通分量时使用了栈,如果跑完 Tarjan 缩点之后呈现出的拓扑序更大,在 Tarjan 会更晚被遍历到,就会更早地被弹出栈而缩点,分量编号会更小,所以 Tarjan 求得的 SCC 编号相当于 反拓扑序。
算法会把整张图遍历一遍,由于这张图 n 和 m 同阶,计算答案时复杂度为 O(n),因此总复杂度为 O(n)。
代码实现
#include <algorithm>#include <cstdio>#include <stack>using namespace std;const int N = 2e6 + 2;int n, m, dfn[N], low[N], t, tot, head[N], a[N];bool vis[N];stack<int> s;struct node { int to, Next;} e[N];void adde(int u, int v) { e[++tot].to = v; e[tot].Next = head[u]; head[u] = tot;}void Tarjan(int u) { dfn[u] = low[u] = ++t; s.push(u); vis[u] = 1; for (int i = head[u]; i; i = e[i].Next) { int v = e[i].to; if (!dfn[v]) { Tarjan(v); low[u] = min(low[u], low[v]); } else if (vis[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { int cur; ++tot; do { cur = s.top(); s.pop(); vis[cur] = 0; a[cur] = tot; } while (cur != u); }}int main() { scanf("%d%d", &n, &m); for (int i = 1, I, J, A, B; i <= m; i++) { scanf("%d%d%d%d", &I, &A, &J, &B); adde(A ? I + n : I, B ? J : J + n); adde(B ? J + n : J, A ? I : I + n); } tot = 0; for (int i = 1; i <= (n << 1); i++) if (!dfn[i]) Tarjan(i); for (int i = 1; i <= n; i++) { if (a[i] == a[i + n]) { printf("IMPOSSIBLE"); return 0; } } puts("POSSIBLE"); for (int i = 1; i <= n; i++) printf("%c%c", a[i] < a[i + n] ? '1' : '0', " \n"[i == n]); return 0;}