2-sat 学习笔记

2-sat 学习笔记

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

思想

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

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

考虑如何构造,将强连通分量所点后得到一个 DAG,考虑结合拓扑序构造,若两个点中拓扑序在前的那个点为 true,则另一个显然为 true,所以取 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 的条件,我们可以考虑用另一个点指向这个点,这样另一个点绝对不能为 true,也就成功限制了这个条件。

推导型

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

逻辑或型

形如 ab 的条件,通过这个条件我们能够推出,¬ab,¬ba,直接连边即可

逻辑与型

形如 ab 的条件,通过这个条件我们能够推出 a=true,b=true

逻辑异或型

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

常见优化

无用点忽略

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

传递点加入

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

posted @   DycIsMyName  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示