第一道2-sat题,各种坑,各种变态,~,只因为建图的时候多乘了个2,让跪了好几次,而为了找这个错误发了我一整天的时间,真心伤不起~~
下面简单说说2-sat的解题步骤:
1,构图,难点
2,求有向图的最大强连通分量
3,缩点,并重新反向建图
4,拓扑排序后,进行染色
5,有的题需要二分求答案
一、关于模型:
一个2-SAT模型应该是一个满足以下的条件的满足性问题:
1、该模型中存在2n个可以分成n组的元素,每组两个元素。
2、每组元素中,选择了其中一个元素,另外一个元素就不能被选择。这两个元素记为a和!a。
3、该模型中的元素之间存在一些关系,且这些关系是对称的。(除非是同一组元素中的关系,这些关系限定了“必须选择”该组中的某一个元素,可能单独出现)
满足上述条件,要求在满足给定关系的情况下在每组元素中选出一个元素的问题称为2-SAT问题。问是否存在即2-SAT判定问题,当然也可以求出一组可行解。
(上面的关于2-SAT的定义是非常不严谨不专业的,仅为帮助大家理解..可能有错,望大家指出..)
当你看见一道题目,一些事物只有唯一且互斥的两种选择(比如两种取值,两种连接方式等等),那么可以分析下题目给出的条件是否满足对称性,若满足则可通过构图将题目转化成2-SAT问题了。
二、关于构图
要解2-SAT问题或者完成判定问题,首要的任务是构图。从《由对称性解2-SAT问题》这篇论文里,我们可以知道,构图的关键是找到冲突。
若a和b冲突,即选a时不能选b,那么选a时必须选!b(因为不选b就必须选!b,这是一个2-SAT问题必须满足的条件),那么我们就连边<a,!b>。同样的道理,如果选了b,那么就不能选a,必须选!a,所以连边<b,!a>。这样的连边,显然是对称的。具体的例子可以看上面的论文。
总之,弄清楚一点:如果存在边<a,b>,那么表示选择a时必须选择b。只要将所有这种“必须”关系都变成边,然后再判定2-sat或者求解2-sat,就能解决2-SAT问题了。
三、关于解2-SAT
方法是,对原图求一次强连通分量,然后看每组中的两个点是否属于同一个强连通分量,如果存在这种情况,那么无解
然后对于缩点后的图G',我们将G'中所有边转置。进行拓扑排序。
对于缩点后的所有点,我们先预处理求出所有冲突顶点。例如缩点后Ai所在强连通分支的ID
为id[ Ai] ,同理~Ai在 id[ ~Ai ],所以冲突顶点
conflict[ id[Ai] ]=conflict[ id[~Ai] ];
同理conflict[ id[~Ai] ]=conflict[ id[Ai] ];
设缩点后有Nscc个点。
然后对拓扑序进行染色,初始化所有点color均为未着色
顺序遍历得到的拓扑序列,对于未着色的点x,将x染成红色,同时将所有与x矛盾的点conflic[x]染成
蓝色。
2-sat的一组解就等价于所有缩点后点颜色为红色的点,也就是color[ id[i] ]=RED的所有点
AC代码:
#include <iostream> #include<string.h> #include<algorithm> #include<cstdio> #define N 105 #define CLR(arr,val) memset(arr,val,sizeof(arr)) using namespace std; typedef struct { int from; int to; int next; }Node; Node node[10*N],node1[10*N]; int head[N],head1[N],low[N],dnf[N],belong[N],Stack[N],Queue[N],Conflict[N],In[N],Color[N]; bool isstack[N]; int res,ans,tot,Count,k,index,n,m; void init() { CLR(head,-1); CLR(head1,-1); CLR(low,-1); CLR(dnf,-1); CLR(belong,0); CLR(Stack,0); CLR(Queue,0); CLR(Conflict,0); CLR(isstack,false); CLR(In,0); CLR(Color,-1); res=ans=tot=k=index=0; } void add(int a,int b) { node[res].from=a; node[res].to=b; node[res].next=head[a]; head[a]=res++; } void add1(int a,int b) { node1[k].from=a; node1[k].to=b; node1[k].next=head1[a]; head1[a]=k++; } void Tarjan(int x){//求强连通分量 low[x]=dnf[x]=++index; isstack[x]=true; Stack[++tot]=x; for(int i=head[x];i!=-1;i=node[i].next) { int v=node[i].to; if(dnf[v]==-1) { Tarjan(v); if(low[x]>low[v]) low[x]=low[v]; } else if(isstack[v]&&low[x]>dnf[v]) low[x]=dnf[v]; } int j; if(low[x]==dnf[x]) { ans++; do{ j=Stack[tot--]; belong[j]=ans; isstack[j]=false; }while(j!=x); } } void in(int &a) { char ch; while((ch=getchar())<'0'||ch>'9'); for( a=0;ch>='0'&&ch<='9';ch=getchar()) a=a*10+ch-'0'; } void build_graph()//重新建图 { for(int i=0;i!=res;++i){ int u=node[i].from,v=node[i].to; if(belong[u]!=belong[v]){ add1(belong[v],belong[u]); In[belong[u]]++; } } } void Topusort()//拓扑排序+染色 { int h=0,t=0; for(int i=1;i<=ans;++i) if(In[i]==0) Queue[t++]=i; while(h<t) { int id=Queue[h++]; if(Color[id]==-1) { Color[id]=1; Color[Conflict[id]]=2; } for(int i=head1[id];i!=-1;i=node1[i].next) { int v=node1[i].to; if(--In[v]==0) Queue[t++]=v; } } } void slove() { for(int i=0;i!=2*n;++i) if(dnf[i]==-1) Tarjan(i); for(int i=0;i!=n;++i){ if(belong[2*i]==belong[2*i+1]) { puts("bad luck"); return; } Conflict[belong[2*i]]=belong[2*i+1]; Conflict[belong[2*i+1]]=belong[2*i]; } build_graph(); Topusort(); for(int i=2;i!=2*n;i+=2) { if(i!=2) printf(" "); if(Color[belong[i]]==Color[belong[0]]) printf("%dw",i/2); else printf("%dh",i/2); } printf("\n"); } int main() { while(1) { bool flag=false; in(n),in(m); if(!n&&!m) return 0; init(); char s1[105],s2[105]; for(int i=0;i!=m;++i) { int a,b; char c,d; scanf("%s",s1); scanf("%s",s2); sscanf(s1,"%d%c",&a,&c); sscanf(s2,"%d%c",&b,&d); if(c=='h'&&d=='h') { add(2*a+1,2*b); add(2*b+1,2*a); } else if(c=='h'&&d=='w') { add(2*a+1,2*b+1); add(2*b,2*a); } else if(c=='w'&&d=='h') { add(2*a,2*b); add(2*b+1,2*a+1); } else if(c=='w'&&d=='w') { add(2*a,2*b+1); add(2*b,2*a+1); } } add(0,1); slove(); }return 0; }