重學2-sat
給定乙個布林方程,判斷是否存在一組布林變數能滿足這個方程,方程的可行解被稱為\(sat\)。這個問題是\(np-hard\)的。但是如果我們對這個問題加上一些限制,就可以在多項式時間內求解了。設乙個布林方程為:
\((a_1\lor a_2\cdots\lor a_n) \land (b_1\lor b_2\cdots\lor b_n) \cdots\)
如果乙個布林方程滿足\(n<=2\),那麼這就是乙個\(2-sat\)問題。
因為每個布林變數只有兩種取值,所以我們可以將每個布林變數拆成兩個點後建圖。設將\(x\)拆成\(x\)和\(x'\)。\(x\)表示變數\(x\)值為真,\(x'\)表示變數\(x\)值為假。
如果從\(a\)的值,我們能推出\(b\)的值。那我們就在圖中連一條有向邊。比如乙個布林方程方程:\((a_1\lor a_2)\),我們發現如果\(a_1\)為假,那麼\(a_2\)一定為真;同理如果\(a_2\)為假,那麼\(a_1\)一定為真。於是我們在圖中建兩條有向邊:\(a_1'->a_2\)和\(a_2'->a_1\)。這就是最基本的\(2-sat\)建圖方法。
對於乙個變數\(x\),如果點\(x\)和點\(x'\)在同乙個強連通分量中,那麼顯然這個布林方程無解,因為此時\(x\)的值既要為真,又要為假。
如果乙個布林方程存在一組解,對於點\(x\),如果\(x\)的拓撲序在\(x'\)之後,那麼答案就是\(x\)取真。反之,\(x\)就取假。因為這樣能夠避免衝突,構造出一組可行解
先在圖中求一遍強連通分量。可以用\(kosaraj\)或者\(tarjan\)。注意\(kosaraju\)中強連通分量編號的順序就是該圖強連通分量的拓撲序。而在\(tarjan\)則是強聯通分量編號順序則和該圖強連通分量拓撲序相反。
**中強聯通分量用\(kosaraju\)求解
graph::kosaraju();
for (int i = 1; i <= n; ++i)
}puts("possible");
for (int i = 1; i <= n; ++i)
題意求解\(2-sat\)問題題解
對每種情況分別連邊即可。設當前兩個變數分別為\(a\)和\(b\)
\(\begin
a'->b, b'->a & a真b真\\
a'->b', b->a & a真b假 \\
a->b, b'->a' & a假b真 \\
a->b', b->a' & a假b假
\end\)
然後按照上面講的方法跑\(kosaraju\)求解即可。
// author: 23forever
#include #define pb push_back
#define pii pair#define mp make_pair
#define fi first
#define se second
typedef long long ll;
const int maxn = 2000000;
using namespace std;
inline int read()
while (isdigit(c))
return x * w;
}int n, m, bel[maxn + 5];
namespace graph
edge(int _to, int _nxt) : to(_to), nxt(_nxt) {}
} edge[maxm + 5], redge[maxm + 5];
int tot, head[maxn + 5], rtot, rhead[maxn + 5];
inline void addedge(int u, int v)
inline void init()
vector < int > vec;
bool vis[maxn + 5];
void dfs1(int u)
vec.pb(u);
}void dfs2(int u, int cnt)
}void kosaraju()
memset(vis, false, sizeof(vis));
int cnt = 0;
for (int i = vec.size() - 1; ~i; --i) }}
void init() else if (a && !b) else if (!a && b) else
}}int main()
} puts("possible");
for (int i = 1; i <= n; ++i)
return 0;
}
題意每個變數有兩種取值,所以問題就是求解乙個\(2-sat\)題解
\(\begin
a'->b, b'->a & a:h,b:h\\
a'->b', b->a & a:h,b:m \\
a->b, b'->a' & a:m,b:h \\
a->b', b->a' & a:m,b:m
\end\)
只要看有沒有\(x\)和\(x'\)在同乙個強聯通分量即可。
**
// author: 23forever
#include typedef long long ll;
const int maxn = 200;
using namespace std;
inline int read()
while (isdigit(c))
return x * w;
}int n, m, bel[maxn + 5], scc_num;
namespace graph
edge(int _to, int _nxt) : to(_to), nxt(_nxt) {}
} edge[maxm + 5];
int tot, head[maxn + 5];
inline void addedge(int u, int v)
inline void init()
int dfn[maxn + 5], low[maxn + 5], idx;
bool in_sta[maxn + 5];
stack < int > sta;
void dfs(int u) else if (in_sta[v])
} if (dfn[u] == low[u]) while (k != u);
}} void tarjan() }}
void init() else if (c1 == 'h' && c2 == 'm') else if (c1 == 'm' && c2 == 'h') else
}}int main()
}if (!f) puts("good");
} return 0;
}
求解\(2-sat\)問題其實是通過巧妙的建圖方法來解決。這種能推出就拉邊的思想十分常見。在並查集相關題目中也能見到。值得積累。 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完之後沒有判為無解,那麼以...