什么是 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\) 的情况。

分析

本题就是要找出哪些状态互相影响,如果两条路同向会相交,则设为冲突,每条边对应两个值,奇数为内圈,偶数为外圈,冲突则将内圈和另一边的外圈相连,视为必须同时存在。

判断好后就用 2-SAT 的最基本,最暴力的做法 dfs。

对于未选中的一边,先选上内圈,进行 dfs 观察是否矛盾,如果不矛盾就可以选它,否则,就换外圈再次尝试,如果二者皆矛盾,便说明无解。

矛盾指无法避免内圈外圈同时被选中,因为我们边的意思为“必须”。

代码

#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;//当次访问到,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;
}
struct noe{
	int l,r;
}c[105];
bool check(int i,int j){
	int a1=c[i].l,b1=c[i].r,a2=c[j].l,b2=c[j].r;
	if(a1==a2||b1==b2||a2==b1||b2==a1)return 0;//端点相重合 
	if(b1<a2||b2<a1)return 0;//碰都碰不到 
	if(a1<a2&&b2<b1)return 0;
	if(a2<a1&&b1<b2)return 0;//包含关系 
	return 1;//冲突 
}
int main()
{
	read(n);
	read(m);
	int x,y;
	for(register int i=1;i<=m;++i){
		read(c[i].l);read(c[i].r);
		if(c[i].l>c[i].r)swap(c[i].l,c[i].r);
	}
	for(int i=1;i<=m;i++){
		for(int j=i+1;j<=m;j++){
			if(check(i,j)){//互异的方向相连 
				add(i*2,j*2-1);
				add(i*2-1,j*2);
				add(j*2,i*2-1);
				add(j*2-1,i*2);
			}
			
		}
	}
	for(register int i=1;i<=m;++i){
		timec++;
		if(xuan[i*2-1]&&xuan[i*2]){//内外都被前面的被迫选了 
			cout<<"Impossible"<<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;//先跑内圈 
			if(bo==0){//内圈不行 
				timec++;
				xuan[i*2-1]=0;
				if(!dfs(i*2)){
					cout<<"Impossible"<<endl;//内外都不行 
					return 0;
				}
			}
		}
	}
	for(register int i=1;i<=m*2;++i){
		if(xuan[i]){
			if(i&1)cout<<'o';//好像o是外圈?不过这并不影响做题 
			else cout<<'i';
		}
	}
}
posted on 2021-09-15 21:40  漠寒·  阅读(45)  评论(0编辑  收藏  举报