介绍
n
n
n 个变量
a
i
a_i
ai,每个变量能且只能选
0
/
1
0/1
0/1
给出若干条件,形如:
(
n
o
t
)
(not)
(not)
a
i
a_i
ai
o
p
t
opt
opt
(
n
o
t
)
(not)
(not)
a
j
=
0
/
1
,
o
p
t
=
a
n
d
,
o
r
,
x
o
r
a_j = 0/1,opt = and,or,xor
aj=0/1,opt=and,or,xor
求出满足所有限制的一组
a
a
a
做法
每个点拆成
i
i
i 和
i
+
n
i+n
i+n,分别表示选取和不选取,下面用
i
′
i'
i′ 表示
i
+
n
i+n
i+n
定义有向边
u
→
v
u \rightarrow v
u→v,表示选择了
u
u
u 就必须选择
v
v
v
然后对所有关系连边,包括逆否命题的连边:
- i , j i,j i,j 不能同时选,即选了 i i i 就要选 j ′ : i → j ′ 、 j → i ′ a i j':i \rightarrow j'、j \rightarrow i' \quad a_i j′:i→j′、j→i′ai x o r xor xor a j = 1 a_j = 1 aj=1
- i , j i,j i,j 必须同时选,即选了 i i i 就要选 j : i → j 、 j → i a i j:i \rightarrow j、j \rightarrow i \qquad a_i j:i→j、j→iai x o r xor xor a j = 0 a_j = 0 aj=0
- i , j i,j i,j 至少选一个,即选了 i ′ i' i′ 就要选 j : i ′ → j 、 j ′ → i a i j:i' \rightarrow j、j' \rightarrow i \quad a_i j:i′→j、j′→iai o r or or a j = 1 a_j = 1 aj=1
- i i i 必须选: i ′ → i , a i = 1 i' \rightarrow i,a_i = 1 i′→i,ai=1
那么对于一个强联通分量里的点,肯定是全选或全不选
用
t
a
r
j
a
n
tarjan
tarjan 缩点后,即可选出可行解,对于每个变量
x
x
x 有四种情况:
- x x x 和 ¬ x \neg x ¬x 毫无关系:任意取
- x ⇒ ¬ x x \Rightarrow \neg x x⇒¬x:取 x x x 为假
- ¬ x ⇒ x \neg x \Rightarrow x ¬x⇒x:取 x x x 为真
- x ⇒ ¬ x x \Rightarrow \neg x x⇒¬x 并且 ¬ x ⇒ x \neg x \Rightarrow x ¬x⇒x:无解
那么缩点后,若
x
x
x 与
¬
x
\neg x
¬x 在同个强联通分量里则无解, 否则选取拓扑序较大的那么点
但是注意:
t
a
r
j
a
n
tarjan
tarjan 里的染色顺序是逆拓扑,我们可以直接用这个染色顺序来求解
也就是若 col[i] < col[i+n]
,则直接选择
i
i
i
一、求任意解
例题一:P4782 【模板】2-SAT 问题
用 t a r j a n tarjan tarjan 染完色后,染色顺序就是逆拓扑
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pii = pair <int,int>;
const int maxn = 2e6 + 5;
int n, m, tot, top, numc;
int dfn[maxn], low[maxn], st[maxn];
int vis[maxn], col[maxn];
vector <int> g[maxn];
void tarjan(int u){
dfn[u] = low[u] = ++tot;
st[++top] = u;
vis[u] = 1;
for(auto v : g[u]){
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]){
++numc;
while(st[top+1] ^ u){
col[st[top]] = numc;
vis[st[top--]] = 0;
}
}
}
int main() {
scanf("%d%d", &n, &m);
for(int i=1; i<=m; i++){
int a, va, b, vb;
scanf("%d%d%d%d", &a, &va, &b, &vb);
// g[a + n * (va & 1)].push_back(b + n * (vb ^ 1));
// g[b + n * (vb & 1)].push_back(a + n * (va ^ 1));
if(va && vb){
g[a+n].push_back(b);
g[b+n].push_back(a);
} else if(va && !vb){
g[a+n].push_back(b+n);
g[b].push_back(a);
} else if(!va && vb){
g[a].push_back(b);
g[b+n].push_back(a+n);
} else {
g[a].push_back(b+n);
g[b].push_back(a+n);
}
}
for(int i=1; i<=n<<1; i++)
if(!dfn[i]) tarjan(i);
for(int i=1; i<=n; i++)
if(col[i] == col[i+n]){
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=1; i<=n; i++)
printf("%d ", col[i] < col[i+n]);
}
二、求字典序最小的解
例题二:HDU 1814 Peaceful Commission
这里用暴力 D F S DFS DFS,实际复杂度是 n 2 n^2 n2 的,下面的例题会说到
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pii = pair <int,int>;
const int maxn = 2e5 + 5;
int n, m, top;
int vis[maxn], st[maxn];
vector <int> g[maxn];
bool dfs(int u) {
if(vis[u^1]) return false;
if(vis[u]) return true;
vis[u] = 1;
st[++top] = u;
for(auto v : g[u])
if(!dfs(v)) return false;
return true;
}
bool solve() {
for(int i=0; i<n<<1; i+=2) {
if(!vis[i] && !vis[i+1]) {
top = 0;
if(!dfs(i)) {
while(top) vis[st[top--]] = 0;
if(!dfs(i+1)) return false;
}
}
}
return true;
}
int main() {
while(~scanf("%d%d", &n, &m)) {
memset(vis, 0, sizeof(vis));
for(int i=0; i<n<<1; i++) g[i].clear();
for(int i=1, a, b; i<=m; i++) {
scanf("%d%d", &a, &b);
a--, b--;
g[a].push_back(b ^ 1);
g[b].push_back(a ^ 1);
}
if(!solve()) puts("NIE");
else
for(int i=0; i<n<<1; i++)
if(vis[i]) printf("%d\n", i + 1);
}
}
例题三:CCPC wannafly day2 J 邦邦的2-SAT模板
本题在于说明,当构造的图是一条链时
上面这种暴力
D
F
S
DFS
DFS的复杂度是
n
2
n^2
n2 的
#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pii = pair <int,int>;
int n;
int main() {
scanf("%d", &n);
printf("%d\n", n);
for(int i=1; i<n; i++)
printf("%d %d\n", -i, i+1);
printf("%d %d\n", -n, -n);
}