拆點 : 將每個 bool 變數拆成 0, 1 兩個點.
連邊 : 將限制條件轉化為連邊.
圖是 dag 時, 對於每個 bool 變數, 合法點的拓撲序大於非法點.
證明 : 若某個 bool 變數拆分成的兩個點為 u,v , 若 u 為非法點, 則存在一條從 u 到 v 的路徑, 所以 v 的拓撲序一定大於 u 的拓撲序.
當圖不是 dag 時, 用 tarjan 演算法將強聯通分量縮成乙個點, 把原圖變為 dag.
tarjan 求完強聯通分量後不用縮點, 判斷兩個點所屬的強聯通分量序號即可, 序號小的為合法點.
證明 : 根據 tarjan 求強聯通分量的過程可知, 若存在一條從點 u 到點 v 的路徑, 則 v 所屬的強聯通分量一定會在 u 所屬的強聯通分量之前被統計到, 所以點 v 所屬的強聯通分量序號更小.
當乙個 bool 變數拆分成的兩個點在同乙個強聯通分量裡, 則無解.
輸出方案時,選擇 scc 編號較小的那個點,且不需要從它開始 dfs 把它所連的點選中,這樣不會產生矛盾。
證明:假設變數 \(n\) 的兩個點 \(x,y\) 的 \(scc\) 編號分別為 \(a,b\ (a < b)\),若 \(x\) 連向點 \(x'\),則必定會有一條邊從 \(y'\) 連向 \(y\),
設 \(x',y'\) 的 \(scc\) 編號分別為 \(c,d\),則有 \(c < a, b < d\)(由第5點可以得到),
又因為 \(a,所以 \(c,所以 \(x'\) 點一定會被選到。
【模板】2-sat 問題
#include #include using namespace std;
const int _ = 2e6 + 7;
int n, m, dfn[_], low[_], cnt, stk[_], top, scc[_], num;
bool vis[_], b[_];
int lst[_], nxt[_], to[_], tot;
int gi()
void add(int x, int y)
void tarjan(int u)
if (low[u] == dfn[u])
}void dfs(int u)
int main()
for (int i = 1; i <= n + n; ++i)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; ++i)
if (vis[i] or vis[i + n]) continue;
scc[i] < scc[i + n] ? vis[i] = 1 : vis[i + n] = 1;
//dfs(scc[i] < scc[i + n] ? i : i + n); // 不需要 dfs,直接選擇 scc 較小的點即可。
} puts("possible");
for (int i = 1; i <= n; ++i) printf("%d ", vis[i] ? 0 : 1);
putchar('\n');
return 0;
}
2 SAT學習筆記
由對稱性解2 sat問題 2 sat解法 上面兩篇 很清楚的介紹了什麼是2 sat以及一些原理演算法 2 sat問題是圖論中乙個比較有意思的問題,重點是建圖,對於邊的意思,就是如果你選了i,就必須選j。2 sat問題有個很明顯的地方就是對於每個i,i包含兩個點,i表示選第乙個點,i 表示選第二個點,...
2 SAT學習筆記
2 sat問題指的是,給你若干個0 1變數。並給你一些限制,讓你求滿足這些限制的可行解 字典序最小的解 限制的種類包括以下幾種 1 a一定是1 2 a一定不是1 3 a,b至少有1個是1 4 a,b最多有乙個是1 5 a,b一定相同 6 a,b一定不同 一般思路是對於乙個變數,建立兩個變數 正,反 ...
2 sat學習筆記
例 struct twosat x xval or y yval void add clause int x,int xv,int y,int yv void init int n bool solve return1 為什麼不需要回溯呢?因為以前定下的變數如果在一輪dfs完之後沒有判為無解,那麼以...