2-SAT学习笔记

SAT问题的定义

SAT是Satisfiability(可满足性)的缩写,而k-SAT问题就是说你有(n)个未知的bool变量(a_i),有(m)个对这些变量的限制,每个限制有(k)个元素,设每个元素为(x),要求 (x)( eg x)(lor)(或)或者(land)(和)的值(如3-SAT中,形如(a or b or !c)a and !b and !c 的条件)为true,或定义某一个未知数为true/false的一种解法。

很容易想到(O(2^nm))暴力做法,即枚举每一个bool未知数的值,判断是否合法。而对于(k geq 3)而言的k-SAT已经被证明是NP完全问题(即没有多项式复杂度的问题),而我们研究的2-SAT则有一个多项式解法。

2-SAT问题的解法

定义对于任何一个单字母bool变量(a)true

我们通过建一些特殊的图来实现:我们令编号从(1)(n)的点为未知变量(a_i)true的情况,(n+1)(2 imes n)的点为(a_i)false的情况。连边方面,我们令从(a)连向(b)表示由(a)能推到(b),则(aland b)可以变为(a ightarrow b, b ightarrow a),即由(a)为真可以推出(b)为真,由(b)为真可以推出(a)为真;(a lor b)可以变为( eg a ightarrow b, eg b ightarrow a),即若(a)为假则(b)为真;(a = x (xin { true, false}))可以变为( eg a ightarrow a),这个我们之后再说为什么,先当这种情况不存在。可以发现所有情况都可以化为刚才三种情况之一。

然后我们对于这样一个特殊的图跑一边强连通分量,如果存在( eg a)(a)只有同存在于同一个强连通分量中,则一定无解,因为这一意味着你可以从( eg a)推到(a),而很明显这是矛盾的。

于是我们得到了一张拓扑图。有一个性质,就是在有解的情况下,一个未知数的两个取值是有前后推导关系的,也就是一个取值直接或间接的指向了另一个取值时,我们取后者是一定有解的。实际实现上,输出的时候,输出对于每一个点拆除的truefalse中,拓扑序较大的那一个,则一定有一个解。然而由于我们跑了Tarjan,那么我们可以附带着求一个反向的拓扑序,那么这就拓扑序也求得了。

从刚才这一角度来说,对于刚才建图中的第三种情况,我们连( eg a ightarrow a)可以让(a)的拓扑序必定大于( eg a),即最后输出的时候一定会输出开始时给它的定义。

很明显复杂度瓶颈在Tarjan跑强连通分量上,其复杂度为(O(n+m)),所以总复杂度为(O(n+m))

例题

例题1 P4782 【模板】2-SAT 问题

一道模板题,甚至连赋值操作都没有。可以看到这里输入的时候用了一些小技巧来压了一下输入的行,其他就没什么难以理解的了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
namespace ztd{
    using namespace std;
    typedef long long ll;
	const int mod = 998244353;
	const int inf = 2147483647;
    template<typename T> inline T read(T& t) {//fast read
        t=0;short f=1;char ch=getchar();double d = 0.1;
        while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
        while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
        if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
        t*=f;
        return t;
    }
}
using namespace ztd;
const int maxn = 2e6+7, maxm = 2e6+7;
int n, m;

int last[maxn], ecnt;
struct edge{int y, gg;}e[maxm];
inline void addedge(int x, int y){
	e[++ecnt] = {y, last[x]};
	last[x] = ecnt;
}

int dfn[maxn], low[maxn], pcnt, st[maxn], top, gang[maxn], gtot;
bool instack[maxn];
void Tarjan(int x){
	dfn[x] = low[x] = ++pcnt; 
	st[++top] = x; instack[x] = 1;
	for(int i = last[x], y = e[i].y; i; i = e[i].gg, y = e[i].y){
		if(!dfn[y]){
			Tarjan(y);
			low[x] = min(low[x], low[y]);
		}else if(instack[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x]){
		int now; ++gtot;
		do{
			now = st[top--]; instack[now] = 0;
			gang[now] = (gtot);
		}while(now ^ x);
	}
}

int main(){
	read(n); read(m);
	int xx, yy, hmx, hmy;
	for(int i = 1; i <= m; ++i){
		read(xx); read(hmx); read(yy); read(hmy); 
		addedge(xx+(1-hmx)*n, yy+hmy*n);
		addedge(yy+(1-hmy)*n, xx+hmx*n);
	}
	for(int i = 1; i <= n*2; ++i) if(!dfn[i]) Tarjan(i);
	for(int i = 1; i <= n; ++i){
		if(gang[i] == gang[i+n]){
			puts("IMPOSSIBLE");
			return 0;
		} 
	}
	puts("POSSIBLE");
	for(int i = 1; i <= n; ++i){
		cout << ((int)(gang[i] > gang[i+n])) << " ";
	} 
	cout << '
';
	return 0;
}

例题2 P4171 [JSOI2010]满汉全席

同样也是一道模板题。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
namespace ztd{
    using namespace std;
    typedef long long ll;
	const int mod = 998244353;
	const int inf = 2147483647;
    template<typename T> inline T read(T& t) {//fast read
        t=0;short f=1;char ch=getchar();double d = 0.1; short hm=-1;
        while (ch<'0'||ch>'9') {if (ch=='-') f=-f; if(ch=='h')hm=1; if(ch=='m')hm=0; ch=getchar();}
        while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
        if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
        t*=f;
        return hm;
    }
}
using namespace ztd;
const int maxn = 407, maxm = 4007;
int n, m, Eastward_Red_Sun_Rise_up_China_got_with_a_LKY;

int last[maxn], ecnt;
struct edge{int y, gg;}e[maxm];
inline void addedge(int x, int y){
	e[++ecnt] = {y, last[x]};
	last[x] = ecnt;
}

int dfn[maxn], low[maxn], pcnt, st[maxn], top, gang[maxn], gtot;
bool instack[maxn];
void Tarjan(int x, int fa){
	dfn[x] = low[x] = ++pcnt; 
	st[++top] = x; instack[x] = 1;
	for(int i = last[x], y = e[i].y; i; i = e[i].gg, y = e[i].y){
		if(!dfn[y]){
			Tarjan(y,x);
			low[x] = min(low[x], low[y]);
		}else if(instack[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x]){
		int now; ++gtot;
		do{
			now = st[top--]; instack[now] = 0;
			gang[now] = (gtot);
		}while(now ^ x);
	}
}

inline void init(){
	n = m = gtot = top = pcnt = ecnt = 0;
	memset(last, 0, sizeof(last));
	memset(dfn, 0, sizeof(dfn));
	memset(gang, 0, sizeof(gang));
	memset(low, 0, sizeof(low));
	memset(instack, 0, sizeof(instack));
}
inline void Main(){
	init();
	read(n); read(m);
	int xx, yy, hmx, hmy;
	for(int i = 1; i <= m; ++i){
		hmx = read(xx); hmy = read(yy);
		addedge(xx+(1-hmx)*n, yy+hmy*n);
		addedge(yy+(1-hmy)*n, xx+hmx*n);
	}
	for(int i = 1; i <= n*2; ++i) if(!dfn[i]) Tarjan(i, -1);
	for(int i = 1; i <= n; ++i){
		if(gang[i] == gang[i+n]){
			puts("BAD");
			return;
		} 
	}
	puts("GOOD");
}

int main(){
//	freopen("P4171_1.in","r",stdin);
	int T; read(T);
	while(T--) Main();	
	return 0;
} 

例题3 P3825 [NOI2017]游戏

这道题中在不考虑 三个都可以选的地图 的情况下,也是一道2-SAT模板题:对于每一个输入(i h_i j h_j),令(x = [ ext{第i个位置能不能选}h_i])(y = [ ext{第j个位置能不能选}h_j]),依次考虑:

  1. (x)false,则关系不可能成立,continue即可;
  2. (y)false,则(x)为假,连(x ightarrow eg x)
  3. 否则则连(x ightarrow y, eg x ightarrow eg y)

然后就是常规的2-SAT了。

我们再考虑进去 三个都可以选的地图(x), 我们可以暴力枚举每一个(x)不能选哪一个,则复杂度为(O((n+m)3^n)),过不了。考虑优化。我们想到我们只要正确的(x)的解存在于我们考虑过的集合就可以这个性质,所以说当我们令(x)(A)(B)的时候,他能选的集合就已经为({a,b,c})三种所有的可能了,则时间复杂度优化到(O((n+m)2^n))了,可过。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
namespace ztd{
    using namespace std;
    typedef long long ll;
	const int mod = 998244353;
	const int inf = 2147483647;
    template<typename T> inline T read(T& t) {//fast read
        t=0;short f=1;char ch=getchar();double d = 0.1;
        while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
        while (ch>='0'&&ch<='9') t=t*10+ch-'0',ch=getchar();
        if(ch=='.'){ch=getchar();while(ch<='9'&&ch>='0') t+=d*(ch^48),d*=0.1,ch=getchar();}
        t*=f;
        return t;
    }
}
using namespace ztd;
const int maxn = 1e5+7, maxm = 4e5+7;
int n, m, d, Dongfang_hong, Taiyang_Sheng, Zhongguo_Chulege_LKY, X[maxn], a1[maxm], b1[maxm];
char s[maxn], a2[maxm], b2[maxm];
int last[maxn], ecnt;
struct edge{int y, gg;}e[maxm];
inline void addedge(int x, int y){
	e[++ecnt] = {y, last[x]};
	last[x] = ecnt;
}

int dfn[maxn], low[maxn], pcnt, st[maxn], top, gang[maxn], gtot;
bool instack[maxn];
void Tarjan(int x){
	dfn[x] = low[x] = ++pcnt; 
	st[++top] = x; instack[x] = 1;
	for(int i = last[x], y = e[i].y; i; i = e[i].gg, y = e[i].y){
		if(!dfn[y]){
			Tarjan(y);
			low[x] = min(low[x], low[y]);
		}else if(instack[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x]){
		int now; ++gtot;
		do{
			now = st[top--]; instack[now] = 0;
			gang[now] = (gtot);
		}while(now ^ x);
	}
}
inline void init(){
	ecnt = pcnt = top = 0;
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(gang, 0, sizeof(gang));
	memset(instack, 0, sizeof(instack));
	memset(last, 0, sizeof(last));
}
char sol[maxn];
inline bool check(){
	for(int i = 1; i <= n*2; ++i) if(!dfn[i]) Tarjan(i);
	for(int i = 1; i <= n; ++i){
		if(gang[i] == gang[i+n]) return 0;
		else if(gang[i] < gang[i+n]) sol[i] = ((s[i] == 'A') ? 'B' : 'A');
		else sol[i] = ((s[i] == 'C') ? 'B' : 'C');
	}
	for(int i = 1; i <= n; ++i) cout << sol[i];
	return 1;
}
inline bool work(){
	init();
	for(int i = 1; i <= m; ++i){
		if(a2[i] == s[a1[i]]) continue;
		if(b2[i] == s[b1[i]]){
			if(a2[i] == 'C' || (a2[i] == 'B' && s[a1[i]] == 'C')) addedge(a1[i]+n, a1[i]);
			else addedge(a1[i], a1[i]+n);
			continue;
		}
		int add1, add2;
		if(a2[i] == 'C' || (a2[i] == 'B' && s[a1[i]] == 'C')) add1 = n; else add1 = 0;
		if(b2[i] == 'C' || (b2[i] == 'B' && s[b1[i]] == 'C')) add2 = n; else add2 = 0;
		addedge(a1[i]+add1, b1[i]+add2); addedge( b1[i]-add2+n, a1[i]-add1+n);
	}
	return check();
}
void dfs(int x){
	if(x == d+1){
		if(work()) exit(0);
		else return;
	}
	s[X[x]] = 'A'; dfs(x+1);
	s[X[x]] = 'B'; dfs(x+1);
}
int main(){
	read(n); read(Zhongguo_Chulege_LKY); scanf("%s", s+1); read(m);
	for(int i = 1; i <= n; ++i) s[i] -= ('a'-'A');
	for(int i = 1; i <= n; ++i) if(s[i] == 'X') X[++d] = i;
	for(int i = 1; i <= m; ++i) scanf("%d %c %d %c",&a1[i], &a2[i], &b1[i], &b2[i]);
	dfs(1);
	puts("-1");
	return 0;
}

原文地址:https://www.cnblogs.com/zimindaada/p/2_SAT.html