什么是 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';
}
}
}