2-SAT 問題
引入
有 \(n\) 個布爾變量 \(x_1 \sim x_n\) 另有 \(m\) 個需要滿足的條件 每個條件的形式都是 \(「x_i 為 true / false 或 x_j 為 true / false」\) 比如 \(「x_1 為真或 x_3 為假」\)、\(「x_7 為假或 x_2 為假」\)。
求一組解 \(x_i\) 使條件滿足
求解
2-SAT 問題的目標是給每個變量賦值使得所有條件得到滿足
也就是讓這一串布爾變量賦值后滿足布爾方程
2 則是指的對於每一個約束涉及兩個變量
將每一個條件中的兩個變量設為 \(a\) 與 \(b\) 給定的條件為 a 或 b 也就是兩個條件只能滿足一個
若給出 \(a\) \(1\) \(b\) \(0\) 則要求 \(a\) 為真時 \(b\) 為假 或是 \(a\) 為假時 \(b\) 為真
則可以理解為:若 \(a\) 假 則 \(b\) 必假 若 \(b\) 真 則 \(a\) 必真
只要想辦法滿足這兩個要求就好了
建圖: 以 \(1 \sim n\) 表示變量為真 以 \(n + 1 \sim 2n\) 表示變量為假(根據個人習慣)
則上面給出的例子就可以表示為: 由 \(n + x\) 向 \(n + y\) 連邊 由 \(y\) 向 \(x\) 建邊 (邊為有向邊)
然后采用 \(tarjan\) 求強聯通分量
對於求得的強聯通分量中 若是存在 \(x\) \(0\) 與 \(x\) \(1\) 在同一強聯通分量中 則該問題無解 否則一定有解
求可行性解:求完強聯通分量后 我們拿到了一張拓撲圖 在這張圖中 若是選擇其中的一個點 則這個點所指向的點也必須選擇
\(tarjan\) 求得的強聯通分量的標號其實就是拓撲逆序 也就是反向的拓撲序 那么一種選擇策略就是在每一個變量的真與假中選擇其拓撲序較大的點 就可以構造出一組可行解
例題
1. 洛谷 P4782 【模板】2-SAT 問題
題目以及思路如上
code:
/*
Time: 2.1
Worker: Blank_space
Source: P4782 【模板】2-SAT 問題
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定義*/
vector <int> e[C << 1];
int n, m, dfn[C << 1], low[C << 1], d[C << 1], top, cnt, st[C << 1], sum;
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt; st[++top] = u;
for(int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if(!dfn[v]) {tarjan(v); low[u] = min(low[u], low[v]);}
else if(!d[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
sum++;
while(st[top] != u) d[st[top--]] = sum;
d[st[top--]] = sum;
}
}
/*----------------------------------------函數*/
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
int x = read(), a = read(), y = read(), b = read();
e[x + n * (a ^ 1)].push_back(y + n * (b & 1));//a 假 則 b 真
e[y + n * (b ^ 1)].push_back(x + n * (a & 1));//b 假 則 a 真
}
for(int i = 1; i <= (n << 1); i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= n; i++) if(d[i] == d[i + n]) {puts("IMPOSSIBLE"); return 0;}
puts("POSSIBLE");
for(int i = 1; i <= n; i++) printf("%d ", d[i] > d[i + n]);
return 0;
}
2. 洛谷 P5782 和平委員會
題目:
給定 \(2n\) 個點 點 \(2i - 1\) 與 點 \(2i\) 兩點中僅取一個點 給定 \(m\) 個條件 規定給出的兩個點不同時取
判斷 是否存在一組解滿足條件 若存在 輸出升序的一組解
思路:
與上面的例題大同小異
\(2i - 1\) 號點 與 \(2i\) 號點 相當於真與假 兩者取一
假定給出的不能同時取的兩個點為 \(a\) \(b\) (此處假定 \(a\) \(b\) 為奇數 偶數同理)
若是不取 \(a\) 則必須取 \(a + 1\) 我們建一條由 \(b\) 指向 \(a + 1\) 的邊(此處只關心 \(a\) 的奇偶性)
若是不取 \(b\) 則必須取 \(b + 1\) 我們建一條由 \(a\) 指向 \(b + 1\) 的邊(此處只關心 \(b\) 的奇偶性)
\(tarjan\) 求強聯通分量 按照強聯通分量的標號取點即可
code:
/*
Time: 2.1
Worker: Blank_space
Source: #10097. 「一本通 3.5 練習 5」和平委員會
2-SAT 問題 板子
*/
/*--------------------------------------------*/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
/*--------------------------------------頭文件*/
const int A = 1e4 + 7;
const int B = 1e5 + 7;
const int C = 1e6 + 7;
const int D = 1e7 + 7;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
const int FFF = 0x8fffffff;
/*------------------------------------常量定義*/
vector <int> e[A << 1];
int n, m, dfn[A << 1], low[A << 1], st[A << 1], top, cnt, sum, d[A << 1];
/*------------------------------------變量定義*/
inline int read() {
int x = 0, f = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
return x * f;
}
/*----------------------------------------快讀*/
void tarjan(int u)
{
dfn[u] = low[u] = ++cnt; st[++top] = u;
for(int i = 0; i < e[u].size(); i++)
{
int v = e[u][i];
if(!dfn[v]) {tarjan(v); low[u] = min(low[u], low[v]);}
else if(!d[v]) low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
sum++;
while(st[top] != u) d[st[top--]] = sum;
d[st[top--]] = sum;
}
}
/*----------------------------------------函數*/
int main()
{
n = read(); m = read();
for(int i = 1; i <= m; i++)
{
int x = read(), y = read();
if(y & 1) e[x].push_back(y + 1); else e[x].push_back(y - 1);
if(x & 1) e[y].push_back(x + 1); else e[y].push_back(x - 1);
}
for(int i = 1; i <= (n << 1); i++) if(!dfn[i]) tarjan(i);
for(int i = 1; i <= (n << 1); i += 2) if(d[i] == d[i + 1]) {puts("NIE"); return 0;}
for(int i = 1; i <= (n << 1); i += 2) printf("%d\n", d[i] < d[i + 1] ? i : i + 1);
return 0;
}
后面如果在做這一類的題目會再補充 先寫到這