#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <iostream> using namespace std; const int maxn = 2E4 + 10; int n, m, first[maxn], Next[maxn * 2], vis[maxn], S[maxn], tol, cnt, a, b; struct Edge { int u, v; Edge(int u = 0, int v = 0): u(u), v(v) {} }; Edge e[maxn * 2]; void Add_Edge(int u, int v) { e[tol].u = u, e[tol].v = v; Next[tol] = first[u]; first[u] = tol++; } int dfs(int u) { if (vis[u ^ 1]) return 0; if (vis[u]) return 1; vis[u] = 1; S[cnt++] = u; for (int i = first[u]; i != -1; i = Next[i]) if (!dfs(e[i].v)) return 0; return 1; } int Two_SAT() { for (int i = 0; i < n; i += 2) if (!(vis[i] || vis[i ^ 1])) { cnt = 0; if (!dfs(i)) { while (cnt) vis[S[--cnt]] = 0; if (!dfs(i ^ 1)) return 0; } } return 1; } int main(int argc, char const * argv[]) { while (~scanf("%d%d", &n, &m)) { n <<= 1; tol = 0; memset(first, -1, sizeof(first)); memset(vis, 0, sizeof(vis)); for (int i = 0; i < m; i++) { scanf("%d%d", &a, &b); --a; --b; Add_Edge(a, b ^ 1); Add_Edge(b, a ^ 1); } if (Two_SAT()) for (int i = 0; i < n; i += 2) printf("%d\n", (vis[i] ? i : i ^ 1) + 1); else printf("NIE\n"); } return 0; }
求字典序最小解的2-sat
i、j矛盾则建边< i , ~j > , < j , ~i >。
然后从小到大注意考虑没有赋值的变量 i,首先标记节点i<<1,并且沿着可行边(图中的有向边)标记所有能标记的节点。如果标记过程中发现某个变量对应的两个节点都被标记,则会引起矛盾,那么说明一开始的假设是错误的。回退到开头将所有过程中的标记清除,然后标记i<<1|1,按照上面的方法继续,如果无论标记i<<1或者i<<1|1都会引起矛盾,则整个2-sat无解。否则得到的所有赋值的节点构成的解正是字典序最小解。