【整理】2-SAT
2-satisfiability,我们一般将其缩写为 2-sat。
了解全名有助于我们对这个算法的理解。 百度翻译:‘satisfiability’---“可满足性,适定性”。
由于SAT问题目前是NP问题,所以自然有最大化满足性问题———MAX-SAT。
然后也有最基本的问题,生存or死亡,嫁给我or吃屎———2-SAT。
如果能解决k-sat问题,那么他一定会火,毕竟没有很好的算法去解决,目前我们研究得更多是2-sat。
之前做的两个2-sat题:nmphy的2-sat。第三个是今天做的,整理一下。
-----------------------------------------------------我是分界线----------------------------------------------------------------
浅谈2-sat:(假设读者已经知道了2sat的原理,只是有时会乱,不知道把谁作为点,谁作为边,不知道怎么建图是好)
一般会有两个或者多个限制,要选择其中一个作为不相容限制。
然后其他的条件作为有向图,然后判环:
【关键】:整个算法转化成图的关键就是找好对象,判断出哪个作为不相容限制。
如果不相容限制的两个子都不能满足,那么结果为false。
【不相容限制】:n个被选择,每个是‘真’or‘假’,代表二者不能同时存在。
【选择限制】:m个要求,一般牵涉到两个不相容限制。
判断是哪种限制:不相容限制再每个集合都存在,而选择限制不是。
【例一】
(HDU1814):题目大意:一国有n个党派,每个党派在议会中都有2个代表,现要组建和平委员会,要从每个党派在议会的代表中选出1人,一共n人组成和平委员会。已知有一些代表之间存在仇恨,也就是说他们不能同时被选为和平委员会的成员,现要你判断满足要求的和平委员会能否创立?如果能,请任意给出一种方案。
【不相容限制】 是每个党派的代表,设为1,2。去了一个,另一个就不能去,每个党派(集合)都存在这样的关系。
【选择限制】 是存在仇恨的代表,并不是所有党派或者代表都存在这样的关系。
附上代码和注释
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <vector> using namespace std; #define R 1//red 为ok #define B 2//black 访问过但不ok #define W 0//white 待染色 const int maxn = 16005; vector<int>G[maxn]; int cnt,col[maxn],ans[maxn],n,m; bool dfs(int u) { if (col[u] == B) return false; if (col[u] == R) return true; col[u] = R;col[u^1] = B;ans[cnt++]=u;//记录染了哪些,以便失败后把颜色改回来 for(int i=0;i<G[u].size();i++) if (!dfs(G[u][i])) return false; return true; } bool _solve() { int i, j; memset(col,0,sizeof(col)); for (i=0; i<n; i++){ if (col[i]) continue; cnt=0; if (!dfs(i)){ for (j=0;j<cnt;j++){ col[ans[j]]=W;//漂白 col[ans[j]^1]=W;//漂白 } if (!dfs(i^1)) return false;//2-sat失败 } } return true; } int main() { int i,a,b; while (~scanf("%d %d",&n, &m)){ n<<=1; for(i=0;i<=n;i++) G[i].clear(); while (m--){ scanf("%d %d",&a, &b); a--;b--; G[a].push_back(b^1); G[b].push_back(a^1); } if (_solve()){ for (i=0; i<n; i++) if(col[i] == R) printf("%d\n",i+1); } else printf("NIE\n"); } return 0; }
【例二】
(HDU1824):集训是辛苦的,道路是坎坷的,休息还是必须的。经过一段时间的训练,lcy决定让大家回家放松一下,但是训练还是得照常进行,lcy想出了如下回家规定,每一个队(三人一队)或者队长留下或者其余两名队员同时留下;每一对队员,如果队员A留下,则队员B必须回家休息下,或者B留下,A回家。由于今年集训队人数突破往年同期最高记录,管理难度相当大,lcy也不知道自己的决定是否可行,所以这个难题就交给你了,呵呵,好处嘛~,免费**漂流一日。
【不相容限制】 对于每个人,留或者去,二选一;
【选择限制】 对于一对人,二留一;对于一队人,队员或队长不能同时离开。
®:例二有三个限制,如果没有选择好哪个是不相容限制,很可能给作图造成困难,最后爆炸。
(每次先选小的一个。保证了字典序最小)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <vector> using namespace std; #define R 1 #define B 2 #define W 0 const int maxn = 80000; vector<int>G[maxn]; int cnt,col[maxn],ans[maxn],n,m; bool _dfs(int u) { if (col[u] == B) return false; if (col[u] == R) return true; col[u] = R;col[u^1] = B;ans[cnt++]=u; for(int i=0;i<G[u].size();i++) if (!_dfs(G[u][i])) return false; return true; } bool _solve() { int i, j; memset(col,0,sizeof(col)); for (i=0; i<n*6; i++){ if (col[i]) continue; cnt=0; if (!_dfs(i)){ for (j=0;j<cnt;j++){ col[ans[j]]=W; col[ans[j]^1]=W; } if (!_dfs(i^1)) return false; } } return true; } int main() { int i,a,b,c; while (~scanf("%d %d",&n, &m)){ for(i=0;i<n*6;i++) G[i].clear(); for(i=1;i<=n;i++){ scanf("%d%d%d",&a,&b,&c); a*=2;b*=2;c*=2; G[a^1].push_back(b); G[a^1].push_back(c); G[b^1].push_back(a); G[c^1].push_back(a); } for(i=1;i<=m;i++){ scanf("%d%d",&a,&b); a*=2;b*=2; G[a].push_back(b^1); G[b].push_back(a^1); } if (_solve()) printf("yes\n"); else printf("no\n"); } return 0; }
【例三】
(hiho1467):有一场音乐会,分为上午、下午两场进行,主办方指定了n首歌让乐队进行演唱。每首歌只会被演唱一次,要么在上午要么在下午。参加音乐会的嘉宾们对于歌曲的演唱时间有一些要求。具体来说,每位嘉宾会指定两首歌曲的演唱时间(上午或者下午)。如果最后实际的演出安排中,两首歌都没有达到嘉宾的要求,那么嘉宾就会对音乐节不滿意。如嘉宾A的要求是上午《我的滑板鞋》和下午《忐忑》,而最后的演出中上午没有《我的滑板鞋》只有《忐忑》,下午没有《忐忑》只有《我的滑板鞋》,那么嘉宾A是不满意的。
音乐节主办方自然希望使所有嘉宾满意,但主办方后来发现有可能不存在一种歌曲的安排方案满足所有嘉宾,所以他们希望你判断一下这种情况是否会发生。
【不相容限制】 对于每首歌,上午或者下午,二选一。(没有不相容对应的点。拆点,假设上午是i,下午是i+n)
【选择限制】 对于每个观众,至少要完成一个要求。
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #include<vector> using namespace std; vector<int>G[420]; int n,m,col[420],ans[420],cnt; const int M=1; const int H=2; const int W=0; int read() { char c=getchar(); int s,a=0; while(c!='h'&&c!='m') c=getchar(); if(c=='h') a=n; scanf("%d",&s); return s+a; } int controt(int u) { if(u>n) return u-n; return u+n; } bool bfs(int u) { if(col[u]==M) return true; if(col[u]==H) return false; col[u]=M;col[controt(u)]=H;ans[++cnt]=u; for(int i=0;i<G[u].size();i++) if(!bfs(G[u][i])) return false; return true; } bool find() { memset(col,0,sizeof(col)); for(int i=1;i<=n+n;i++){ if(col[i]) continue; cnt=0; if(!bfs(i)){ for(int j=1;j<=cnt;j++) { col[ans[j]]=W; col[controt(ans[j])]=W; } cnt=0; if(!bfs(controt(i))) return false; } } return true; } int main() { int i,T,u,v; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(i=1;i<=n+n;i++) G[i].clear(); for(i=1;i<=m;i++){ u=read(); v=read(); G[u].push_back(controt(v)); G[v].push_back(controt(u)); } if(find()) printf("GOOD\n"); else printf("BAD\n"); } return 0; }
【例四】
(hdu1815):有n个牛棚, 还有两个中转站S1和S2, S1和S2用一条路连接起来。 为了使得任意牛棚两个都可以有道路联通,现在要让每个牛棚都连接一条路到S1或者S2。有a对牛棚互相有仇恨,所以不能让他们的路连接到同一个中转站。还有b对牛棚互相喜欢,所以他们的路必须连到同一个中专站。道路的长度是两点的曼哈顿距离。问最小的任意两牛棚间的距离中的最大值是多少?
【二分】假定ans=x
【不相容限制】 a,b选择特定的的S,若不满足dis<=x,则相排斥。
#include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> #include<algorithm> #include<vector> using namespace std; const int maxn=1010; const int B=1; const int R=2; const int W=0; vector<int>G[maxn]; vector<int>G2[maxn]; int dis[3][maxn],Dis;//Dis是S点,T点 int x,y,x1,y1,x2,y2,a,b,n,ans; int col[maxn],q[maxn],num; void init() { for(int i=1;i<=2*n;i++) G[i].clear(); for(int i=1;i<=2*n;i++) G2[i].clear(); ans=-1; } void scan() { int i; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); Dis=abs(x1-x2)+abs(y1-y2); for(i=1;i<=n;i++){ scanf("%d%d",&x,&y); dis[1][i]=abs(x-x1)+abs(y-y1); dis[2][i]=abs(x-x2)+abs(y-y2); } for(i=1;i<=a;i++){ scanf("%d%d",&x,&y); G[x].push_back(y+n); G[x+n].push_back(y); G[y].push_back(x+n); G[y+n].push_back(x); } for(i=1;i<=b;i++){ scanf("%d%d",&x,&y); G[x].push_back(y); G[y].push_back(x); G[x+n].push_back(y+n); G[y+n].push_back(x+n); } } bool dfs(int u) { if(col[u]==R) return false; if(col[u]==B) return true; col[u]=B; col[u>n?u-n:u+n]=R; q[++num]=u; for(int i=0;i<G[u].size();i++) if(!dfs(G[u][i])) return false; for(int i=0;i<G2[u].size();i++) if(!dfs(G2[u][i])) return false; return true; } bool check(int x) { int i,j; for(i=1;i<=n;i++) if(dis[1][i]>x&&dis[2][i]>x) return false; for(i=1;i<=2*n;i++) G2[i].clear(); for(i=1;i<=2*n;i++) col[i]=0; for(i=1;i<=n;i++) for(j=i+1;j<=n;j++){ int d1=dis[1][i]+dis[1][j]; int d2=dis[1][i]+dis[2][j]+Dis; int d3=dis[2][i]+dis[2][j]; int d4=dis[2][i]+dis[1][j]+Dis; if(d1>x&&d2>x&&d3>x&&d4>x) return false; if(d1>x){ G2[i].push_back(j+n); G2[j].push_back(i+n); } if(d2>x){ G2[i].push_back(j); G2[j+n].push_back(i+n); } if(d3>x){ G2[i+n].push_back(j); G2[j+n].push_back(i); } if(d4>x){ G2[i+n].push_back(j+n); G2[j].push_back(i); } } for(i=1;i<=2*n;i++){ if(col[i]) continue; num=0; if(!dfs(i)){ for(j=1;j<=num;j++) { col[q[j]>n?q[j]-n:q[j]+n]=W; col[q[j]]=W; } if(!dfs(i>n?i-n:i+n)) return false; } } return true; } int main() { while(~scanf("%d%d%d",&n,&a,&b)){ init(); int L=0,R=8000000; scan(); while(L<=R){ int mid=(L+R)>>1; if(check(mid)){ ans=mid;R=mid-1;} else L=mid+1; } printf("%d\n",ans); } return 0; }
本文仅阐述如何寻找不相容限制,然后去建图。
所以代码的优化在这里没有提及,用【缩点】和【拓扑】的优化将在下文补充。
如果你对2-sat有什么新的认识,或者疑问,请留言额。待续。。。