什么是\(SAT\)问题

\(k-SAT\)\(k-satisfiability\),中文名叫“\(k\)-适应性问题”,它描述的是这样一类问题。

给你\(n\)个变量\(a_i\),每个变量有\(a_i\)种取值,称变量\(a_i\)的取值集合为\(a_i\)的值域。同时还有一些约束,例如当\(a_i\)取它的值域里某个值时,\(a_j\)的值就不能为\(a_j\)值域里的某个值。

问是否有一种取值方式满足所有的约束。

顾名思义,本题的\(2-SAT\)问题即为\(k\)等于2的情况。

本题处理方法

首先,模型化题中逻辑关系图。

对于两个代表\(A_i\)\(A_j\)不和的情况,连边\(A_i\)\(A_j'\)\(A_j\)\(A_i'\)。边的意思理解为“必须”。

注:\(A_i\)\(A_i'\)是用于描述同一组中两个不同节点。

然后我们就有一种很直接的求解方法,\(dfs\)

对于未选中的一组,先选上第一个,进行\(dfs\)观察是否矛盾,如果不矛盾就可以选他,否则,就换另一个再次尝试,如果二者皆矛盾,便说明无解。

矛盾指无法避免任一组内两个委员同时被选中,因为我们边的意思为“必须”。

这同样可以作为算法正确性的证明。

这个算法时间复杂度应为\(O\)(\(nm\)),对于本题比较极限,数据再大一点可能就通不过了。

代码

#include<bits/stdc++.h>
using namespace std;
int n,m,head[160001],xuan[160001],tot,timec,vis[160001];
struct node{
	int to,next;
}a[40001];//邻接表 
inline void read(int& res){
	res=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){res=(res<<3)+(res<<1)+c-48,c=getchar();}
}
inline void add(int qq,int mm){//加边 
	a[++tot].to=mm,a[tot].next=head[qq],head[qq]=tot;
}
bool dfs(int x){
	xuan[x]=1;
	vis[x]=timec;
	int xx=x-1;
	if(x&1)xx+=2;//确定另一委员编号 
	if(xuan[xx]){//选过,有问题 
		xuan[x]=0;
		return false;
	}
	for(register int i=head[x];i;i=a[i].next){
		if(vis[a[i].to]!=timec){//该点在此次未被访问过 
			if(!dfs(a[i].to)){//后代返回有问题,说明此次深搜的根不可取,将选择退去,并返回不可行 
				for(register int j=head[x];j!=i;j=a[j].next)xuan[a[j].to]=0;
				xuan[a[i].to]=0;
				return false;
			}
		}
	}
	return true;//可行 
}
int main()
{
	read(n);
	read(m);
	int x,y;
	for(register int i=1;i<=m;++i){
		read(x);read(y);
		y&1?add(x,y+1):add(x,y-1);
		x&1?add(y,x+1):add(y,x-1);
	}
	for(register int i=1;i<=n;++i){
		timec=i;
		if(xuan[i*2-1]&&xuan[i*2]){//两个在前面的处理中都被选了,做保险用 
			cout<<"NIE"<<endl;
			return 0;
		}
		int bo=0;
		if(xuan[i*2-1]||xuan[i*2]){//选过,不需要再选 
			bo=1;
		}
		if(bo==0){
			if(dfs(i*2-1))bo=1;//1号 
			if(bo==0){
				xuan[i*2-1]=0;
				if(!dfs(i*2)){//2号 
					cout<<"NIE"<<endl;
					return 0;
				}
			}
		}
	}
	for(register int i=1;i<=n*2;++i){
		if(xuan[i])printf("%d\n",i);//输出答案 
	}
}

posted on 2021-08-28 21:24  漠寒·  阅读(78)  评论(0编辑  收藏  举报