這兩天學習了一下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
View Code
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 3683
pku 3678
pku3678
pku 3648
pku3648
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
pku2723
pku 2749
pku2749
pku 3905
View Code
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
求字典序最小的解,暴力。。。
View Code
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 }
其他的問題以后遇到了再更新。。。
