什么是\(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);//输出答案
}
}