2-SAT

定义

2-SAT问题如下:给出 n n n个集合,每个集合内有两个元素(例如: t r u e true true f a l s e false false),要求每个集合内必须取且只能取一个元素,并且给出一些限制条件,例如选 i i i就不能选 j j j,求一种合法方案。

分析

首先如果集合 i i i中有两个元素,那么可以把它们标号为 2 ∗ ( i − 1 ) 2*(i-1) 2(i1) 2 ∗ ( i − 1 ) + 1 2*(i-1)+1 2(i1)+1,这两个元素可以通过异或1转换,则如果 i i i j j j不能同时取,我们需要建两条边: i − > j i -> j i>j KaTeX parse error: Expected group after '^' at position 1: ^̲ 1,j->i KaTeX parse error: Expected group after '^' at position 1: ^̲ 1。 这里的建边表示选择,也就是说i->j如果有边,说明选i,就必选j。

解法1

暴力DFS,枚举每个集合先选其中一个元素,如果不合法,撤销换另一个元素,如果还是不行,证明没有合法方案,退出。DFS时向所有连边的元素判断,如果x和x+1都选上就是非法的,退出。

非常暴力的方法。复杂度也略高 O ( n e ) O(ne) O(ne),但是限制少,范围多。可以求字典序最小解。也可以处理非对称情况。

模板

Hihocoder 1468 HDU 1814

#include
#include
#define maxn 16005
#define maxe 40005
using namespace std;
int n,e,tot,son[maxe],nxt[maxe],lnk[maxn],top,stk[maxn];
bool vs[maxn];
void _add(int x,int y){son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;}
bool _dfs(int x){
	if (vs[x]) return 1;
	if (vs[x^1]) return 0;
	vs[x]=1; stk[++top]=x;
	for (int j=lnk[x];j;j=nxt[j])
		if (!_dfs(son[j])) return 0;
	return 1;
}
bool Two_SAT(){
	memset(vs,0,sizeof(vs));
	for (int i=0;i<2*n;i+=2)
		if (!vs[i]&&!vs[i^1]){
			top=0; if (_dfs(i)) continue;
			while (top) vs[stk[top--]]=0;
			if (!_dfs(i^1)) return 0;
		}
	return 1;
}
int main()
{
	freopen("peace.in","r",stdin);
	freopen("peace.out","w",stdout);
	while (scanf("%d%d",&n,&e)==2){
		tot=0; memset(lnk,0,sizeof(lnk));
		for (int i=1,x,y;i<=e;i++){
			scanf("%d%d",&x,&y); x--; y--;
			_add(x,y^1); _add(y,x^1);
		}
		if (!Two_SAT()) printf("NIE\n"); else
		for (int i=0;i<n*2;i++) if (vs[i]) printf("%d\n",i+1);
	}
	return 0;
}

解法2

把建好的图拿出来,如果一个SCC(强连通分量)中的点只要选1个,那么整个SCC都要选,所以可以对建好的图求强连通,如果i和i^1在同一个SCC里,那么就无解,但是如果没有呢?怎么找到解?

以下内容由于我说(kan)不(bu)清(dong),所以直接拿ZigZagK的话引用

我们将新图的反图进行拓扑排序,就可以得到反图中每个点所在SCC的拓扑序。看图(注意这是新图不是反图):

在这里插入图片描述
由此可见,如果反图中i所在SCC的拓扑序>反图中i^ 1所在SCC的拓扑序且存在路径使i->i^ 1,我们就不能够选i,因为这将导致i^ 1被选。所以在i和i^ 1中我们需要选择反图拓扑序较小的元素(如果i和i^ 1之间没有路径显然可以随意选)。这样可以求出一组任意解,但显然不能求出字典序最小(大)的解。

前面一直说着反图反图,但实际上并不需要真的去重新建图,因为我们关注的是拓扑序,而反图只不过是拓扑序反一下的事情。我们还可以惊奇的发现,反图的拓扑序其实就是Tarjan生成SCC的顺序,这个yy一下很好得出:由于Tarjan会递归调用下去,所以反图拓扑序小的SCC会优先被处理出来。有了这些发现,代码实现就变得非常简单。

这种想法看似天衣无缝,但实际上一定要在2-SAT满足对称性的情况下才正确。

对称性:如果i能够到j,那么j^ 1就能够到i^1。 若2-SAT的限制都是常规限制(选i不能选j),那么该2-SAT肯定满足对称性。这个非常显然就不用解释啦:P。 如果不满足对称性,就可能会出现这种情况导致矛盾
2-SAT_第1张图片

而在满足对称性的情况下,就不会出现该情况。

综上,解法2的复杂度为 O ( n + e ) O(n+e) O(n+e) (Tarjan复杂度),但是当 e e e达到 n 2 n ^2 n2级别时,就需要其他优化,不过,这都是后话了……

模板

POJ3683

#include
#include
using namespace std;
const int maxn=2002,maxe=maxn*maxn;
int n,k,tot,ti,son[maxe],nxt[maxe],lnk[maxn],st[maxn],gl[maxn],dfn[maxn],low[maxn],top,stk[maxn],SCC[maxn];
bool instk[maxn],ans[maxn];
bool _check(int i,int j) {return gl[i]>st[j]&&gl[j]>st[i];}
void _add(int x,int y){son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;}
void _tarjan(int x){
	low[x]=dfn[x]=++ti; stk[++top]=x; instk[x]=1;
	for (int j=lnk[x];j;j=nxt[j])
		if (!dfn[son[j]]) {_tarjan(son[j]); low[x]=min(low[x],low[son[j]]);}
		else if (instk[son[j]]) low[x]=min(low[x],dfn[son[j]]);
	if (dfn[x]==low[x]){
		int y; k++;
		do {y=stk[top--]; instk[y]=0; SCC[y]=k;}
		while (y!=x);
	}
}
inline void readi(int &x){
	x=0; char ch=getchar();
	while ('0'>ch||ch>'9') ch=getchar();
	while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
int main()
{
	freopen("wedding.in","r",stdin);
	freopen("wedding.out","w",stdout);
	readi(n); tot=ti=k=0; n<<=1;
	for (int i=0,x,y,s,t,D;i<n;i+=2){
		readi(x); readi(y); s=x*60+y;
		readi(x); readi(y); t=x*60+y;
		readi(D); if (s+D>t) {printf("NO"); return 0;}
		st[i]=s; gl[i]=s+D; st[i^1]=t-D; gl[i^1]=t;
	}
	for (int i=0;i<n-2;i+=2) for (int ta=0;ta<2;ta++)
	for (int j=i+2;j<n;j+=2) for (int tb=0;tb<2;tb++)
		if (_check(i^ta,j^tb)) {_add(i^ta,j^tb^1); _add(j^tb,i^ta^1);}
	for (int i=0;i<n;i++) if (!dfn[i]) _tarjan(i);
	for (int i=0;i<n;i+=2)
		if (SCC[i]==SCC[i^1]) {printf("NO"); return 0;}
		else if (SCC[i]<SCC[i^1]) ans[i]=1; else ans[i^1]=1;
	printf("YES\n");
	for (int i=0;i<n;i++)
		if (ans[i]) printf("%02d:%02d %02d:%02d\n",st[i]/60,st[i]%60,gl[i]/60,gl[i]%60);
	return 0;
}

Hihocoder1467

i i i j j j至少选一个,那么i^1 ->j, j^1->i

#include
#include
#include
using namespace std;
const int maxn=205,maxe=4005;
int n,m,k,tot,ti,top,tst,son[maxe],nxt[maxe],lnk[maxn],dfn[maxn],low[maxn],stk[maxn],SCC[maxn];
bool instk[maxn];
void _add(int x,int y){son[++tot]=y; nxt[tot]=lnk[x]; lnk[x]=tot;}
void _tarjan(int x){
	low[x]=dfn[x]=++ti; stk[++top]=x; instk[x]=1;
	for (int j=lnk[x];j;j=nxt[j])
		if (!dfn[son[j]]) {_tarjan(son[j]); low[x]=min(low[x],low[son[j]]);}
		else if (instk[son[j]]) low[x]=min(low[x],dfn[son[j]]);
	if (dfn[x]==low[x]){
		int y; k++;
		do {y=stk[top--]; instk[y]=0; SCC[y]=k;}
		while (y!=x);
	}
}
inline void readi(int &x){
	x=0; char ch=getchar(),lst='m';
	while ('0'>ch||ch>'9') {lst=ch; ch=getchar();}
	while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	x=(x-1)<<1; if (lst=='h') x++;
}
void _init(){
	memset(lnk,0,sizeof(lnk));
	memset(low,0,sizeof(low));
	memset(dfn,0,sizeof(dfn));
	memset(instk,0,sizeof(instk));
	scanf("%d%d",&n,&m); tot=ti=k=0;
	for (int i=1,x,y;i<=m;i++){
		readi(x); readi(y); _add(x^1,y); _add(y^1,x);
	}
}
void _solve(){
	for (int i=0;i<n*2;i++) if (!dfn[i]) _tarjan(i);
	for (int i=0;i<n;i+=2)
		if (SCC[i]==SCC[i^1]) {printf("BAD\n"); return ;}
	printf("GOOD\n");
}
int main()
{
	freopen("concert.in","r",stdin);
	freopen("concert.out","w",stdout);
	scanf("%d",&tst);
	while (tst--){
		_init();
		_solve();
	}
	return 0;
}

你可能感兴趣的:(2-SAT,======图论======)