【2-SAT】学习小结
这两天学习了一下2-SAT,主要参考了以下两个资料:
另外还有这个博客我觉得讲得挺白话的。
建图:
2-SAT问题远没有网络流那样复杂,只要抓住关系建好图基本就直接可以解了,在这类问题中建边的规则就是“必须”,对于边<i,j>,它的意义就是选择了i就必须选j。
对于题目中给出的每对关系都可以化成下面的几种形式:
A,B不能同时取 <A,B'><B,A'>
A,B不能都不取 <A',B><B',A>
A,B必须都取或者都不取 <A,B><B,A><A',B'><B',A'>
必须取A <A',A>
主要的模型就这四个,其余的都可以通过徳摩根律化成上面的形式,前三个的建图都不难理解,对于第四个,这样建边是因为如果选了A'就必须选A,显然是不符合逻辑的,也就是说A'不可选,那么自然要选择A了。
算法
两种解法,第一种就是在图上直接暴力,递归+回溯来找可行解,时间复杂度O(n*m),这个算法比较慢,但是也不是毫无用武之地,如果要求字典序最小或者什么别的特殊要求的解的话,那么只能这样暴力枚举了。
如果仅仅是构造一个可行解的话,那么用论文里给的办法就行,也就是强连通缩点,然后判不相容的两个点是不是在一个连通分支。然后对新图边取反拓扑排序,每次把未染色的连通分支及和它对立的连通分支染色。至于为什么这么做,我还没有彻底明白,有待思考
入门练习
2-SAT本身就是个判定性问题,最基本的题型就是给定一些关系然后判定是否可解什么的,当然很多时候还会套上别的东西,一般的就是二分枚举答案+2-SAT判定是否有解。
还有一类就是在判定可解性之后,构造一个可行性方案。
pku 3207
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 #define N 1010 6 using namespace std; 7 struct Edge{ 8 int u,v,next; 9 Edge(){} 10 Edge(int _u,int _v,int _next){ 11 u=_u;v=_v;next=_next; 12 } 13 }edge[N*N]; 14 int dfn[N],low[N],id[N],stack[N*N],cc; 15 int head[N],cnt,dep,top; 16 int n,m; 17 bool instack[N]; 18 void init(){ 19 memset(head,-1,sizeof(head)); 20 memset(instack,0,sizeof(instack)); 21 cnt=0; 22 } 23 void addedge(int u,int v){ 24 edge[cnt]=Edge(u,v,head[u]);head[u]=cnt++; 25 } 26 void tarjan(int u){ 27 int v; 28 dfn[u]=low[u]=++dep; 29 stack[++top]=u; 30 instack[u]=1; 31 for(int k=head[u];k!=-1;k=edge[k].next){ 32 v=edge[k].v; 33 if(!dfn[v]){ 34 tarjan(v); 35 low[u]=min(low[u],low[v]); 36 }else if(instack[v]) 37 low[u]=min(low[u],dfn[v]); 38 } 39 if(dfn[u]==low[u]){ 40 ++cc; 41 do{ 42 v=stack[top--]; 43 instack[v]=0; 44 id[v]=cc; 45 }while(v!=u); 46 } 47 } 48 void solve(){ 49 dep=cc=top=0; 50 memset(dfn,0,sizeof(dfn)); 51 for(int i=1;i<=2*m;i++) 52 if(!dfn[i])tarjan(i); 53 } 54 int seg[N][2]; 55 bool check(int i,int j){ 56 return seg[i][0]<seg[j][0]&&seg[j][0]<seg[i][1]&&seg[i][1]<seg[j][1]; 57 } 58 int main(){ 59 while(~scanf("%d%d",&n,&m)){ 60 for(int i=1;i<=m;i++) 61 scanf("%d%d",&seg[i][0],&seg[i][1]); 62 init(); 63 for(int i=1;i<=m;i++) 64 for(int j=i+1;j<=n;j++) 65 if(check(i,j)||check(j,i)){ 66 addedge(i,i+m); 67 addedge(j,i+m); 68 addedge(i+m,j); 69 addedge(j+m,i); 70 } 71 solve(); 72 bool flag=1; 73 for(int i=1;i<=m;i++){ 74 if(id[i]==id[i+m]){ 75 flag=0; 76 break; 77 } 78 } 79 if(flag) 80 puts("panda is telling the truth..."); 81 else 82 puts("the evil panda is lying again"); 83 } 84 return 0; 85 }
pku 3683
pku 3678
pku 3648
1 /* 2 建边规则:<x,y>表示如果x在新娘对面,那么y必须也在新娘对面。 3 最后在新娘新郎之家建一条边表示新郎必须和新娘相对。 4 输出方案的时候要判断该点颜色是否和新娘的一致。 5 */ 6 7 #include<cstdio> 8 #include<cstring> 9 #include<algorithm> 10 #include<vector> 11 #include<queue> 12 #define N 110 13 using namespace std; 14 struct Edge{ 15 int u,v,next; 16 Edge(){} 17 Edge(int _u,int _v,int _next){ 18 u=_u;v=_v;next=_next; 19 } 20 }edge[N*N]; 21 int head[N],cnt; 22 int dfn[N],low[N],stack[N],id[N],num,dep,top; 23 bool instack[N]; 24 vector<int>V[N]; 25 int c[N],deg[N]; 26 void init(){ 27 memset(head,-1,sizeof(head)); 28 cnt=0; 29 } 30 void add(int u,int v){ 31 edge[cnt]=Edge(u,v,head[u]);head[u]=cnt++; 32 } 33 void tarjan(int u){ 34 int v; 35 dfn[u]=low[u]=++dep; 36 stack[++top]=u; 37 instack[u]=1; 38 for(int k=head[u];k!=-1;k=edge[k].next){ 39 v=edge[k].v; 40 if(!dfn[v]){ 41 tarjan(v); 42 low[u]=min(low[u],low[v]); 43 }else if(instack[v]){ 44 low[u]=min(low[u],dfn[v]); 45 } 46 } 47 if(dfn[u]==low[u]){ 48 ++num; 49 do{ 50 v=stack[top--]; 51 instack[v]=0; 52 id[v]=num; 53 }while(u!=v); 54 deg[num]=0; 55 V[num].clear(); 56 } 57 } 58 int n; 59 int x[N]; 60 int opp(int x){ 61 return x<n?(x+n):(x-n); 62 } 63 bool solve(){ 64 memset(dfn,0,sizeof(dfn)); 65 memset(instack,0,sizeof(instack)); 66 dep=num=top=0; 67 for(int i=0;i<n*2;i++) 68 if(!dfn[i])tarjan(i); 69 for(int i=0;i<n;i++){ 70 if(id[i]==id[i+n])return 0; 71 x[id[i]]=id[i+n]; 72 x[id[i+n]]=id[i]; 73 } 74 return 1; 75 } 76 void topo(){ 77 queue<int>Q; 78 for(int i=0;i<2*n;i++) 79 for(int k=head[i];k!=-1;k=edge[k].next){ 80 int v=edge[k].v; 81 if(id[i]!=id[v]){ 82 V[id[v]].push_back(id[i]); 83 deg[id[i]]++; 84 } 85 } 86 memset(c,0,sizeof(c)); 87 for(int i=1;i<=num;i++) 88 if(deg[i]==0)Q.push(i); 89 while(!Q.empty()){ 90 int p=Q.front();Q.pop(); 91 if(c[p]==0){ 92 c[p]=1; 93 c[x[p]]=-1; 94 } 95 for(int i=0;i<V[p].size();i++){ 96 --deg[V[p][i]]; 97 if(deg[V[p][i]]==0)Q.push(V[p][i]); 98 } 99 } 100 } 101 int main(){ 102 int m,x,y; 103 char a,b; 104 while(~scanf("%d%d",&n,&m)){ 105 if(!(n||m))return 0; 106 init(); 107 for(int i=1;i<=m;i++){ 108 scanf("%d%c %d%c",&x,&a,&y,&b); 109 if(a=='h')x+=n; 110 if(b=='h')y+=n; 111 add(x,opp(y)); 112 add(y,opp(x)); 113 } 114 add(0,opp(0)); 115 if(!solve()){ 116 puts("bad luck"); 117 continue; 118 } 119 topo(); 120 bool flag=0; 121 for(int i=1;i<n;i++){ 122 if(flag)printf(" "); 123 flag=1; 124 if(c[id[i]]==c[id[0]]) 125 printf("%dw",i); 126 else 127 printf("%dh",i); 128 } 129 puts(""); 130 } 131 return 0; 132 }
pku 2723
pku 2749
pku 3905
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define N 2010 5 using namespace std; 6 struct Edge{ 7 int u,v,next; 8 Edge(){} 9 Edge(int _u,int _v,int _next){ 10 u=_u;v=_v;next=_next; 11 } 12 }edge[N*N]; 13 int head[N],cnt; 14 int dfn[N],low[N],stack[N],id[N],num,dep,top; 15 bool instack[N]; 16 void init(){ 17 memset(head,-1,sizeof(head)); 18 cnt=0; 19 } 20 void add(int u,int v){ 21 edge[cnt]=Edge(u,v,head[u]);head[u]=cnt++; 22 } 23 void tarjan(int u){ 24 int v; 25 dfn[u]=low[u]=++dep; 26 stack[++top]=u; 27 instack[u]=1; 28 for(int k=head[u];k!=-1;k=edge[k].next){ 29 v=edge[k].v; 30 if(!dfn[v]){ 31 tarjan(v); 32 low[u]=min(low[u],low[v]); 33 }else if(instack[v]){ 34 low[u]=min(low[u],dfn[v]); 35 } 36 } 37 if(dfn[u]==low[u]){ 38 ++num; 39 do{ 40 v=stack[top--]; 41 instack[v]=0; 42 id[v]=num; 43 }while(u!=v); 44 } 45 } 46 int n; 47 bool solve(){ 48 memset(dfn,0,sizeof(dfn)); 49 memset(instack,0,sizeof(instack)); 50 num=dep=top=0; 51 for(int i=1;i<=2*n;i++) 52 if(!dfn[i])tarjan(i); 53 for(int i=1;i<=n;i++) 54 if(id[i]==id[i+n])return 0; 55 return 1; 56 } 57 int main(){ 58 int m; 59 int x,y; 60 char a[3],b[3]; 61 while(~scanf("%d%d",&n,&m)){ 62 init(); 63 for(int i=1;i<=m;i++){ 64 scanf("%1s%d%1s%d",a,&x,b,&y); 65 if(a[0]=='+'){ 66 if(b[0]=='+'){ 67 add(x+n,y);add(y+n,x); 68 }else{ 69 add(x+n,y+n);add(y,x); 70 } 71 }else{ 72 if(b[0]=='+'){ 73 add(x,y);add(y+n,x+n); 74 }else{ 75 add(x,y+n);add(y,x+n); 76 } 77 } 78 } 79 printf("%d\n",solve()); 80 } 81 return 0; 82 }
hdu 1814
求字典序最小的解,暴力。。。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define N 20010 5 using namespace std; 6 struct Edge{ 7 int u,v,next; 8 Edge(){} 9 Edge(int _u,int _v,int _next){ 10 u=_u;v=_v;next=_next; 11 } 12 }edge[N*10]; 13 int head[N],cnt; 14 int dfn[N],low[N],stack[N],id[N],num,dep,top; 15 bool instack[N]; 16 void init(){ 17 memset(head,-1,sizeof(head)); 18 cnt=0; 19 } 20 void add(int u,int v){ 21 edge[cnt]=Edge(u,v,head[u]);head[u]=cnt++; 22 } 23 int c[N],s[N],cc; 24 int n,m; 25 int opp(int x){ 26 if(x&1)return x+1; 27 else return x-1; 28 } 29 bool dfs(int u){ 30 if(c[u]==1)return 1; 31 if(c[u]==-1)return 0; 32 c[u]=1; 33 c[opp(u)]=-1; 34 s[++cc]=u; 35 for(int k=head[u];k!=-1;k=edge[k].next){ 36 int v=edge[k].v; 37 if(!dfs(v))return 0; 38 } 39 return 1; 40 } 41 int main(){ 42 int a,b; 43 while(~scanf("%d%d",&n,&m)){ 44 init(); 45 for(int i=1;i<=m;i++){ 46 scanf("%d%d",&a,&b); 47 add(a,opp(b)); 48 add(b,opp(a)); 49 } 50 memset(c,0,sizeof(c)); 51 bool flag=1; 52 for(int i=1;i<=2*n&&flag;i++){ 53 if(c[i]!=0)continue; 54 cc=0; 55 if(!dfs(i)){ 56 for(int j=1;j<=cc;j++){ 57 c[s[j]]=0; 58 c[opp(s[j])]=0; 59 } 60 if(!dfs(opp(i)))flag=0; 61 } 62 } 63 if(!flag){ 64 puts("NIE"); 65 continue; 66 } 67 for(int i=1;i<=2*n;i++) 68 if(c[i]==1)printf("%d\n",i); 69 } 70 return 0; 71 }
其他的问题以后遇到了再更新。。。