2-sat 学习笔记

\(\text{2-sat}\) 学习笔记

有这样一类问题,有多个变量 \(a_{1\to n}\),每个变量的取值范围为 \(\{0,1\}\),给出 \(m\) 条限制条件,形如 \((\lor_{i=1}^{k}a_{p_i}=x_i)=\text{true}\) 的形式,需要你求解是否有可行解。这就是 \(\text{k-sat}\) 问题,已被证明是 \(\text{NPC}\) 问题,但 \(\text{2-sat}\) 问题是可以在多项式复杂度内求解的问题。

思想

\(\text{2-sat}\) 问题一般形如 \(n\) 个变量每个变量有 \(2\) 中不同的取值且只能取一个,并给出 \(k=2\) 时的形式限制条件,需要求解可行的变量取值。

考虑将每一个点拆成两个点,分别表示是否选择该取值,那么显然两个中只能有一个满足。以这一条为基础,我们想要证明整个方程无解必须说明当一个取值为 \(\text{true}\) 时,另一个也一定为 \(\text{true}\)。考虑用一条有向边传递这个信息,即 \(a\to b\) 表示若 \(a\)\(\text{true}\)\(b\) 一定为 \(\text{true}\)。那么当且仅当拆成的两个点处在同一个强连通分量时方程无解。

考虑如何构造,将强连通分量所点后得到一个 \(\text{DAG}\),考虑结合拓扑序构造,若两个点中拓扑序在前的那个点为 \(\text{true}\),则另一个显然为 \(\text{true}\),所以取 \(\text{true}\) 的点只能是拓扑序靠后的点。但实际实现中我们没有必要真正去缩点,因为 Tarjan 实现时强连通分量的编号相当于逆拓扑序,也就是反着的拓扑序,我们取所在强连通分量编号较小的那个点就行。时间复杂度为 \(O(n+m)\)\(n\) 为点数,\(m\) 为边数。

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define id(x,t) ((t)?x:x+n)

const int N=1e6+5;

int n,m,cnt,num,top,scc,tot;
int head[N<<1],dfn[N<<1],low[N<<1],col[N<<1],stk[N<<1];
struct Edge{
	int v,to;
}e[N<<1];

il void AddEdge(int x,int y){
	e[cnt]={y,head[x]};
	head[x]=cnt++;
	return ;
}

il void Tarjan(int x){
	dfn[x]=low[x]=++num;
	stk[++top]=x;
	for(int i=head[x];i;i=e[i].to){
		int y=e[i].v;
		if(!dfn[y]){
			Tarjan(y);
			low[x]=min(low[x],low[y]);
		}else if(!col[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		++scc;
		int y;
		do{
			y=stk[top--];
			col[y]=scc;
		}while(y!=x);
	}
	return ;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,a,y,b;cin>>x>>a>>y>>b;
		AddEdge(id(x,a^1),id(y,b));
		AddEdge(id(y,b^1),id(x,a));
	}
	for(int i=1;i<=n*2;i++){
		if(!dfn[i])Tarjan(i);
	}
	for(int i=1;i<=n;i++){
		if(col[i]==col[i+n]){
			cout<<"IMPOSSIBLE";
			return 0;
		}
	}
	cout<<"POSSIBLE\n";
	for(int i=1;i<=n;i++)cout<<(col[i]<col[i+n])<<" ";
	return 0;
}

常见模型

赋值型

形如 \(a=x\) 的条件,我们可以考虑用另一个点指向这个点,这样另一个点绝对不能为 \(\text{true}\),也就成功限制了这个条件。

推导型

形如 \(a\to b\) 的条件,我们可以直接连边,但是这样做不够,因为通过这个命题我们还能推导出一个命题 \(\lnot b\to\lnot a\),这个命题是前面那个命题的逆否命题,是保证正确性必须有的命题。

逻辑或型

形如 \(a\lor b\) 的条件,通过这个条件我们能够推出,\(\lnot a\to b,\lnot b\to a\),直接连边即可

逻辑与型

形如 \(a\land b\) 的条件,通过这个条件我们能够推出 \(a=\text{true},b=\text{true}\)

逻辑异或型

形如 \(a\oplus b\) 的条件,通过这个条件我们能够推出 \(a\to \lnot b,\lnot a\to b,b\to\lnot a,\lnot b\to a\),事实上,我们发现这构成了两个环,因此在某些题目中我们可以利用这一点,将两个环提前缩为一点达成优化时间、空间复杂度的目的。

常见优化

无用点忽略

指一些点只起到传递作用,而没有其他限制作用,这样的点在一定程度上是无用的,为了节省复杂度可以直接删去。

传递点加入

在一些边数过多的图中,我们可以用一些传递点(也就是单独的一个点,没有配对)来传递信息,这样我们可以用一条边代替多条边的作用,起到优化的效果。比如对于 \(n\) 个点每个点向 \(m\) 个点连边,这是 \(O(nm)\) 的,但我们可以新增一个点 \(P\),让 \(n\) 个点先向 \(P\) 连边,接着 \(P\)\(m\) 个点连边,复杂度便降至 \(O(n+m)\)。这个思路在所有的优化建图中均可以使用。

posted @ 2024-12-11 19:13  DycIsMyName  阅读(4)  评论(0编辑  收藏  举报