推荐几篇博客:https://blog.csdn.net/JarjingX/article/details/8521690 研究总结2-sat问题
https://blog.csdn.net/whereisherofrom/article/details/79417926 有向图强连通+2-sat问题
推荐几篇论文:https://wenku.baidu.com/view/afd6c436a32d7375a41780f2.html 由对称性问题解2-sat
https://wenku.baidu.com/view/0f96c3daa58da0116c1749bc.html 2-sat问题解法浅析
tarjan模板
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20010; 6 const int maxm=50010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=1;i<=N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 }
DFS模板(按字典序大小排列)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20020; 6 const int maxm=100010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 bool vis[maxn]; 12 int s[maxn],top,n; 13 14 void init() 15 { 16 tot=0; 17 memset(head,-1,sizeof(head)); 18 } 19 20 void addedge(int u,int v) 21 { 22 edge[tot].to=v; 23 edge[tot].nxt=head[u]; 24 head[u]=tot++; 25 } 26 27 bool dfs(int u) 28 { 29 if ( vis[u^1] ) return false; 30 if ( vis[u] ) return true; 31 vis[u]=true; 32 s[top++]=u; 33 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 34 if ( !dfs(edge[i].to) ) return false; 35 } 36 return true; 37 } 38 39 bool tsat() 40 { 41 memset(vis,false,sizeof(vis)); 42 for ( int i=0;i<2*n;i+=2 ) { 43 if ( vis[i] || vis[i^1] ) continue; 44 top=0; 45 if ( !dfs(i) ) { 46 while ( top ) vis[s[--top]]=false; 47 if ( !dfs(i^1) ) return false; 48 } 49 } 50 return true; 51 }
练习题:
1.(HDOJ1814)http://acm.hdu.edu.cn/showproblem.php?pid=1814
题意:有n个政党,每个政党有两个人,先要组织一个委员会需要从每个政党中选出一人。现有m条关系,代表两个代表的敌对关系,当两个代表有敌对关系时不能同时选择这两个代表
分析:2-sat模板题,需要按照字符串从小到大输出,故采用DFS的做法。对于(a,b)存在敌对的关系,即意味着a和b不能同时被选择,连边a->b'和b->a',进行dfs。注意模板中的标号是从[0,2n)的
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20020; 6 const int maxm=100010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 bool vis[maxn]; 12 int s[maxn],top,n; 13 14 void init() 15 { 16 tot=0; 17 memset(head,-1,sizeof(head)); 18 } 19 20 void addedge(int u,int v) 21 { 22 edge[tot].to=v; 23 edge[tot].nxt=head[u]; 24 head[u]=tot++; 25 } 26 27 bool dfs(int u) 28 { 29 if ( vis[u^1] ) return false; 30 if ( vis[u] ) return true; 31 vis[u]=true; 32 s[top++]=u; 33 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 34 if ( !dfs(edge[i].to) ) return false; 35 } 36 return true; 37 } 38 39 bool tsat() 40 { 41 memset(vis,false,sizeof(vis)); 42 for ( int i=0;i<2*n;i+=2 ) { 43 if ( vis[i] || vis[i^1] ) continue; 44 top=0; 45 if ( !dfs(i) ) { 46 while ( top ) vis[s[--top]]=false; 47 if ( !dfs(i^1) ) return false; 48 } 49 } 50 return true; 51 } 52 53 int main() 54 { 55 int m,i,j,k,x,y,z; 56 bool flag; 57 while ( scanf("%d%d",&n,&m)!=EOF ) { 58 init(); 59 while ( m-- ) { 60 scanf("%d%d",&x,&y); 61 x--;y--; 62 addedge(x,y^1); 63 addedge(y,x^1); 64 } 65 flag=tsat(); 66 if ( flag ) { 67 for ( i=0;i<2*n;i++ ) { 68 if ( vis[i] ) printf("%d ",i+1); 69 } 70 } 71 else printf("NIE "); 72 } 73 return 0; 74 }
2.(HDOJ1816)http://acm.hdu.edu.cn/showproblem.php?pid=1816
题意:有n对钥匙和m扇门,每扇门有2个锁,只要能够打开其中一个锁就算能打开门,只有打开前一扇门才有机会去打开下一扇门。同时每对钥匙只能选取其中一把,而只有当锁和钥匙编号相同时才能开锁。现求最多能开几扇门
分析:对于每对钥匙(a,b),因为最多只能选一把所以有a->'b&&b->'a。而对于每扇门的两个锁(x,y)至少需要打开其中一个,有'x->y&&'y->x。需要二分遍历能够开的门的数量mid,只要前mid扇门能够满足条件即可(即对于门从1到mid添边即可),判断的条件没有一个点和它的否定在一个强连通分量中即算满足条件。
注意:二分右边界的初始值需要为m+1(+1的愿意是保证可以取到右边界,又不会超过边界)。总共有2*n把钥匙,有4*n个点,对于点i来说,点i+2*n为其的否定。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<map> 5 using namespace std; 6 const int maxn=20010; 7 const int maxm=50010; 8 struct edge{ 9 int to,nxt; 10 }edge[maxm]; 11 struct Edge{ 12 int x,y; 13 }door[maxm],key[maxm]; 14 int head[maxn],tot; 15 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 16 int index,top; 17 int scc,n; 18 bool vis[maxn]; 19 int num[maxn]; 20 21 void addedge(int u,int v) 22 { 23 edge[tot].to=v; 24 edge[tot].nxt=head[u]; 25 head[u]=tot++; 26 } 27 28 void tarjan(int u) 29 { 30 int v; 31 low[u]=dfn[u]=++index; 32 stack[top++]=u; 33 vis[u]=true; 34 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 35 v=edge[i].to; 36 if ( !dfn[v] ) { 37 tarjan(v); 38 low[u]=min(low[u],low[v]); 39 } 40 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 41 } 42 if ( low[u]==dfn[u] ) { 43 scc++; 44 do { 45 v=stack[--top]; 46 vis[v]=false; 47 belong[v]=scc; 48 num[scc]++; 49 } 50 while ( v!=u ); 51 } 52 } 53 54 void solve(int N) 55 { 56 memset(dfn,0,sizeof(dfn)); 57 memset(vis,false,sizeof(vis)); 58 memset(num,0,sizeof(num)); 59 index=scc=top=0; 60 for ( int i=0;i<N;i++ ) { 61 if ( !dfn[i] ) tarjan(i); 62 } 63 } 64 65 void init() 66 { 67 tot=0; 68 memset(head,-1,sizeof(head)); 69 } 70 71 bool judge(int mid) 72 { 73 init(); 74 for ( int i=1;i<=n;i++ ) { 75 int u=key[i].x; 76 int v=key[i].y; 77 addedge(u,v+2*n); 78 addedge(v,u+2*n); 79 } 80 for ( int i=1;i<=mid;i++ ) { 81 int u=door[i].x; 82 int v=door[i].y; 83 addedge(u+2*n,v); 84 addedge(v+2*n,u); 85 } 86 solve(4*n); 87 for ( int i=1;i<=mid;i++ ) { 88 if ( belong[i]==belong[i+2*n] ) return false; 89 } 90 return true; 91 } 92 93 int main() 94 { 95 int m,i,j,k,x,y,z,ans,l,r,mid; 96 while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) { 97 for ( i=1;i<=n;i++ ) { 98 scanf("%d%d",&key[i].x,&key[i].y); 99 } 100 for ( i=1;i<=m;i++ ) { 101 scanf("%d%d",&door[i].x,&door[i].y); 102 } 103 l=0; 104 r=m+1; 105 while ( r-l>1 ) { 106 mid=(l+r)/2; 107 if ( judge(mid) ) l=mid; 108 else r=mid; 109 } 110 printf("%d ",l); 111 } 112 return 0; 113 }
3.(POJ3207)http://poj.org/problem?id=3207
题意:一个圆上有n(n <= 1000)个结点,分别按顺序编号0,1,2…n-1,从圆上任意找两个点连接起来,可以选择圆内连,也可以选择圆外连,每个结点只允许连一次。给定m对连接,问是否存在这样一种情况,所得所有连接之间互不相交。
分析:将每条线(每个连接)看作一个状态,一个状态分为圆内连和圆外连两种情况,即将其拆成2个点x,x'。当某两条直接用同一种方式(即都圆内连,或者都圆外连)连接会产生相交时,此时必有一条直接圆内连,另一条直接圆外连,所以建边x-y' y-x' x'-y y-x'。
注意:边的数量可能会非常大,注意存边的数组尽量开大,不然会RE
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20010; 6 const int maxm=500100; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 struct node{ 11 int l,r; 12 }arr[maxm]; 13 int head[maxn],tot; 14 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 15 int index,top; 16 int scc; 17 bool vis[maxn]; 18 int num[maxn]; 19 20 void addedge(int u,int v) 21 { 22 edge[tot].to=v; 23 edge[tot].nxt=head[u]; 24 head[u]=tot++; 25 } 26 27 void tarjan(int u) 28 { 29 int v; 30 low[u]=dfn[u]=++index; 31 stack[top++]=u; 32 vis[u]=true; 33 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 34 v=edge[i].to; 35 if ( !dfn[v] ) { 36 tarjan(v); 37 low[u]=min(low[u],low[v]); 38 } 39 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 40 } 41 if ( low[u]==dfn[u] ) { 42 scc++; 43 do { 44 v=stack[--top]; 45 vis[v]=false; 46 belong[v]=scc; 47 num[scc]++; 48 } 49 while ( v!=u ); 50 } 51 } 52 53 void solve(int N) 54 { 55 memset(dfn,0,sizeof(dfn)); 56 memset(vis,false,sizeof(vis)); 57 memset(num,0,sizeof(num)); 58 index=scc=top=0; 59 for ( int i=1;i<=N;i++ ) { 60 if ( !dfn[i] ) tarjan(i); 61 } 62 } 63 64 void init() 65 { 66 tot=0; 67 memset(head,-1,sizeof(head)); 68 } 69 70 int main() 71 { 72 int n,m,i,j,k,x,y,z,x1,x2,y1,y2; 73 while ( scanf("%d%d",&n,&m)!=EOF ) { 74 init(); 75 for ( i=1;i<=m;i++ ) { 76 scanf("%d%d",&x,&y); 77 x++;y++; 78 if ( x>y ) swap(x,y); 79 arr[i].l=x; 80 arr[i].r=y; 81 } 82 for ( i=1;i<=m;i++ ) { 83 for ( j=i+1;j<=m;j++ ) { 84 x1=arr[i].l; 85 y1=arr[i].r; 86 x2=arr[j].l; 87 y2=arr[j].r; 88 if ( (x1<x2&&x2<y1&&y1<y2) || (x2<x1&&x1<y2&&y2<y1) ) { 89 addedge(i,j+m); 90 addedge(j,i+m); 91 addedge(i+m,j); 92 addedge(j,i+m); 93 } 94 } 95 } 96 solve(2*m); 97 bool flag=true; 98 for ( i=1;i<=m;i++ ) { 99 if ( belong[i]==belong[i+m] ) { 100 flag=false; 101 break; 102 } 103 } 104 if ( flag ) printf("panda is telling the truth... "); 105 else printf("the evil panda is lying again "); 106 } 107 return 0; 108 }
4.(POJ3678)http://poj.org/problem?id=3678
题意:有n个数,m个操作。对于每个操作给出x y z op。op有三种操作,分别为AND OR XOR,该三种操作于计算机二进制中的% | ^三种操作相对于。对于每个操作表示的含义是第x个数和第y个数进行相应的操作使得结果为z。问是否存在这样的一组数使得所有的操作全都满足
分析:一个数的状态分为0(正面)和1(反面)。对于不同操作有不同的建边方式。
对于操作&来说,当z==1时x-x' y-y'(表示均选择反面)。当z==0时,x'-y y-x'(表示反面状态最多选一个)
对于操作|来说,当z==1时 x-y' y-x'(表示反面最少选一个)。当z==0时,x'-x y'-y(表示均选择正面)
对于操作^来说,当z==1时 x-y' y-x' x'-y y'-x(表示正面 反面都要选择一个)。当z==0时,x-y y-x x'-y' y'-x'(表示所选的两个状态需要相同)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2010; 6 const int maxm=5e6; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=1;i<=N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 } 66 67 int main() 68 { 69 int n,m,i,j,k,x,y,z; 70 char s[10]; 71 while ( scanf("%d%d",&n,&m)!=EOF ) { 72 init(); 73 while ( m-- ) { 74 scanf("%d%d%d%s",&x,&y,&z,s); 75 x++;y++; 76 if ( s[0]=='A' ) { 77 if ( z==1 ) { 78 addedge(x,x+n); 79 addedge(y,y+n); 80 } 81 else { 82 addedge(x+n,y); 83 addedge(y+n,x); 84 } 85 } 86 else if ( s[0]=='O' ) { 87 if ( z==1 ) { 88 addedge(x,y+n); 89 addedge(y,x+n); 90 } 91 else { 92 addedge(x+n,x); 93 addedge(y+n,y); 94 } 95 } 96 else if ( s[0]=='X' ) { 97 if ( z==1 ) { 98 addedge(x,y+n); 99 addedge(y,x+n); 100 addedge(x+n,y); 101 addedge(y+n,x); 102 } 103 else { 104 addedge(x,y); 105 addedge(y,x); 106 addedge(x+n,y+n); 107 addedge(y+n,x+n); 108 } 109 } 110 } 111 solve(2*n); 112 bool flag=true; 113 for ( i=1;i<=n;i++ ) { 114 if ( belong[i]==belong[i+n] ) { 115 flag=false; 116 break; 117 } 118 } 119 if ( flag ) printf("YES "); 120 else printf("NO "); 121 } 122 return 0; 123 }
5.(HDOJ4421)http://acm.hdu.edu.cn/showproblem.php?pid=4421
题意:给定一个b[N][N]的矩阵,它是由a[N]矩阵按相应的条件变换而来,问是否可能存在这样的a矩阵
分析:只需要将b[][]矩阵中的数拆成二进制(最多31位),就将这个问题转化成上面的那个问题,但是此题很容易TLE
6.(HDOJ1815)http://acm.hdu.edu.cn/showproblem.php?pid=1815
题意:给定n个牛棚,和两个中转点s1,s2,每个牛棚和中转点的坐标都是已知的,每个牛棚都要连接一个中转点,每个牛棚中的牛可以通过中转点到其他任意一个牛棚。现有两种关系,有点牛之间相互讨厌不能将它们连接到同一个中转点,有点牛相互喜欢必须将它们连接到同一个中转点。求使最长的牛棚间距离最小,两个牛棚之间的距离为曼哈顿距离,现要使最大值最小,求这个最小的最大值是多少。
分析:二分+s-sat。不断二分最小的最大值,再进行构图和2-sat,看是否有出现矛盾情况。对于每个牛棚正面代表与s1相连,反面代表与s2相连 。
对于like关系:x-y y-x x'-y' y'-x'(表示x和y要么同时选,要么同时不选)
对于hate关系:x-y' y-x' x'-y y'-x(表示x,y只能同时选择一个)
而对于两两牛棚之间,有四种关系(都连s1,都连2,一个连s1一个连s2)。此时考虑的是一定不连(>mid)的情况,而不是一定连(<=mid)的情况(其实是可能连,但是建边只能建成一定连)。
dis(i,S1)+dis(S1,j)>mid x-y' y-x'
dis(i,S2)+dis(S2,j)>mid x'-y y'-x
dis(i,S1)+dis(S1,S2)+dis(S2,j)>mid x-y y'-x'
dis(i,S2)+dis(S2,S1)+dis(S1,j)>mid x'-y' y-x
注意:预处理算出每个牛棚到两个中转点之间的距离。对于不存在的情况,当最后r==inf时(没有任何一种情况是满足条件的),输出-1。数组尽量开大
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<algorithm> 5 using namespace std; 6 const int maxn=20010; 7 const int maxm=500010; 8 const int inf=1e8; 9 struct edge{ 10 int to,nxt; 11 }edge[maxm]; 12 int head[maxn],tot; 13 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 14 int index,top; 15 int scc; 16 bool vis[maxn]; 17 int num[maxn]; 18 int A,B,d,n,dis1[maxn],dis2[maxn]; 19 struct node{ 20 int x,y; 21 }aa[maxn],bb[maxn],arr[maxn],s1,s2; 22 bool flag; 23 24 void addedge(int u,int v) 25 { 26 edge[tot].to=v; 27 edge[tot].nxt=head[u]; 28 head[u]=tot++; 29 } 30 31 void tarjan(int u) 32 { 33 int v; 34 low[u]=dfn[u]=++index; 35 stack[top++]=u; 36 vis[u]=true; 37 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 38 v=edge[i].to; 39 if ( !dfn[v] ) { 40 tarjan(v); 41 low[u]=min(low[u],low[v]); 42 } 43 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 44 } 45 if ( low[u]==dfn[u] ) { 46 scc++; 47 do { 48 v=stack[--top]; 49 vis[v]=false; 50 belong[v]=scc; 51 num[scc]++; 52 } 53 while ( v!=u ); 54 } 55 } 56 57 void solve(int N) 58 { 59 memset(dfn,0,sizeof(dfn)); 60 memset(vis,false,sizeof(vis)); 61 memset(num,0,sizeof(num)); 62 index=scc=top=0; 63 for ( int i=1;i<=N;i++ ) { 64 if ( !dfn[i] ) tarjan(i); 65 } 66 } 67 68 void init() 69 { 70 tot=0; 71 memset(head,-1,sizeof(head)); 72 } 73 74 int dis(node a,node b) 75 { 76 return abs(a.x-b.x)+abs(a.y-b.y); 77 } 78 79 bool judge(int mid) 80 { 81 int i,j,k,x,y,x1,x2,y1,y2; 82 init(); 83 for ( i=1;i<=A;i++ ) { 84 x=aa[i].x; 85 y=aa[i].y; 86 addedge(x,y+n); 87 addedge(y,x+n); 88 addedge(x+n,y); 89 addedge(y+n,x); 90 } 91 for ( i=1;i<=B;i++ ) { 92 x=bb[i].x; 93 y=bb[i].y; 94 addedge(x,y); 95 addedge(y,x); 96 addedge(x+n,y+n); 97 addedge(y+n,x+n); 98 } 99 for ( i=1;i<=n;i++ ) { 100 for ( j=i+1;j<=n;j++ ) { 101 if ( dis1[i]+dis1[j]>mid ) { 102 addedge(i,j+n); 103 addedge(j,i+n); 104 } 105 if ( dis1[i]+d+dis2[j]>mid ) { 106 addedge(i,j); 107 addedge(j+n,i+n); 108 } 109 if ( dis2[i]+dis2[j]>mid ) { 110 addedge(i+n,j); 111 addedge(j+n,i); 112 } 113 if ( dis2[i]+d+dis1[j]>mid ) { 114 addedge(i+n,j+n); 115 addedge(j,i); 116 } 117 } 118 } 119 solve(2*n); 120 for ( i=1;i<=n;i++ ) { 121 if ( belong[i]==belong[i+n] ) return false; 122 } 123 return true; 124 } 125 126 int main() 127 { 128 int m,i,j,k,x,y,z,l,r,mid; 129 while ( scanf("%d%d%d",&n,&A,&B)!=EOF ) { 130 scanf("%d%d%d%d",&s1.x,&s1.y,&s2.x,&s2.y); 131 d=dis(s1,s2); 132 for ( i=1;i<=n;i++ ) { 133 scanf("%d%d",&arr[i].x,&arr[i].y); 134 dis1[i]=dis(arr[i],s1); 135 dis2[i]=dis(arr[i],s2); 136 } 137 for ( i=1;i<=A;i++ ) scanf("%d%d",&aa[i].x,&aa[i].y); 138 for ( i=1;i<=B;i++ ) scanf("%d%d",&bb[i].x,&bb[i].y); 139 l=0; 140 r=inf; 141 while ( r-l>1 ) { 142 mid=(l+r)/2; 143 if ( judge(mid) ) r=mid; 144 else l=mid; 145 } 146 if ( r!=inf ) printf("%d ",r); 147 else printf("-1 "); 148 } 149 return 0; 150 }
7.(POJ3648)http://poj.org/problem?id=3648
题意:新娘,新郎和n-1对夫妇,分别坐在长条桌的两边,其中有m对人之间存在奸情(同性和异性皆有可能)。现在有两个限制:1. 任意一对夫妇必须坐在长条桌的两面;
2. m对有奸情的人不能坐在新娘的对面;求一种可能的方案,使得上面两种情况都能满足。
分析:每对夫妇看作一个状态,正面(x)表示女的坐新娘对面,反面(x')表示男的坐新娘对面。首先连一条0-1表示新郎坐新娘对面。对于有奸情的2个人,最多只有一个人坐在新娘对面。最后输出拓朴排序靠后的(即belong[]大的)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20010; 6 const int maxm=50010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=0;i<N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 } 66 67 int main() 68 { 69 int n,m,i,j,k,x,y,z; 70 char c1,c2; 71 while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) { 72 init(); 73 addedge(0,1); 74 for ( i=1;i<=m;i++ ) { 75 scanf("%d%c %d%c",&x,&c1,&y,&c2); 76 x=2*x; 77 if ( c1=='h' ) x^=1; 78 y=2*y; 79 if ( c2=='h' ) y^=1; 80 addedge(x,y^1); 81 addedge(y,x^1); 82 } 83 solve(2*n); 84 bool flag=true; 85 for ( i=1;i<n;i++ ) { 86 if ( belong[i*2]==belong[i*2+1] ) { 87 flag=false; 88 break; 89 } 90 } 91 if ( !flag ) { 92 printf("bad luck "); 93 continue; 94 } 95 for ( i=1;i<n;i++ ) { 96 x=2*i; 97 y=2*i+1; 98 if ( belong[x]<belong[y] ) printf("%dh",i); 99 else printf("%dw",i); 100 if ( i!=n-1 ) printf(" "); 101 else printf(" "); 102 } 103 } 104 return 0; 105 }
也可以将正面表示女的坐新郎对面,反面表示男的坐新郎对面。首先连一条1-0表示新娘坐新郎对面。对于有奸情的2个人,最少有一个人坐在新狼对面。最后输出拓扑排序小的
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=20010; 6 const int maxm=50010; 7 struct edge{ 8 int to,nxt; 9 }edge[maxm]; 10 int head[maxn],tot; 11 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 12 int index,top; 13 int scc; 14 bool vis[maxn]; 15 int num[maxn]; 16 17 void addedge(int u,int v) 18 { 19 edge[tot].to=v; 20 edge[tot].nxt=head[u]; 21 head[u]=tot++; 22 } 23 24 void tarjan(int u) 25 { 26 int v; 27 low[u]=dfn[u]=++index; 28 stack[top++]=u; 29 vis[u]=true; 30 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 31 v=edge[i].to; 32 if ( !dfn[v] ) { 33 tarjan(v); 34 low[u]=min(low[u],low[v]); 35 } 36 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 37 } 38 if ( low[u]==dfn[u] ) { 39 scc++; 40 do { 41 v=stack[--top]; 42 vis[v]=false; 43 belong[v]=scc; 44 num[scc]++; 45 } 46 while ( v!=u ); 47 } 48 } 49 50 void solve(int N) 51 { 52 memset(dfn,0,sizeof(dfn)); 53 memset(vis,false,sizeof(vis)); 54 memset(num,0,sizeof(num)); 55 index=scc=top=0; 56 for ( int i=0;i<N;i++ ) { 57 if ( !dfn[i] ) tarjan(i); 58 } 59 } 60 61 void init() 62 { 63 tot=0; 64 memset(head,-1,sizeof(head)); 65 } 66 67 int main() 68 { 69 int n,m,i,j,k,x,y,z; 70 char c1,c2; 71 while ( scanf("%d%d",&n,&m)!=EOF && (n+m) ) { 72 init(); 73 addedge(1,0); 74 for ( i=1;i<=m;i++ ) { 75 scanf("%d%c %d%c",&x,&c1,&y,&c2); 76 x=2*x; 77 if ( c1=='h' ) x^=1; 78 y=2*y; 79 if ( c2=='h' ) y^=1; 80 addedge(y^1,x); 81 addedge(x^1,y); 82 } 83 solve(2*n); 84 bool flag=true; 85 for ( i=1;i<n;i++ ) { 86 if ( belong[i*2]==belong[i*2+1] ) { 87 flag=false; 88 break; 89 } 90 } 91 if ( !flag ) { 92 printf("bad luck "); 93 continue; 94 } 95 for ( i=1;i<n;i++ ) { 96 x=2*i; 97 y=2*i+1; 98 if ( belong[x]>belong[y] ) printf("%dh",i); 99 else printf("%dw",i); 100 if ( i!=n-1 ) printf(" "); 101 else printf(" "); 102 } 103 } 104 return 0; 105 }
8.(POJ2296)http://poj.org/problem?id=2296
题意:给出以下二维坐标点,然后让你往平面上放正方形,点必须落在正方形上面边的中点或者下面边的中点,正方形不能重叠,可以共用边。问最大正方形边的边长。
分析:二分最大边长。对于某两个点,先判断两点之间的横坐标距离,如果<mid,那么考察两点之间的纵坐标的关系。
纵坐标关系分为三大类:
abs(y1-y2)==0 此时必定一个正方形向上,一个正方形向下
abs(y1-y2)<mid 此时分为y1<y2 || y2<y1,此时两个正方形也必定一个向上一个向下
abs(y1-y2)<2*mid 此时分为y1<y2 || y2<y1 ,此时两个正方形满足不同时面向对方即可
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 using namespace std; 6 const int maxn=20010; 7 const int maxm=50010; 8 const int inf=1e6; 9 struct edge{ 10 int to,nxt; 11 }edge[maxm]; 12 int head[maxn],tot; 13 int low[maxn],dfn[maxn],stack[maxn],belong[maxn]; 14 int index,top; 15 int scc,n; 16 bool vis[maxn]; 17 int num[maxn]; 18 struct node{ 19 int x,y; 20 }arr[maxn]; 21 22 void addedge(int u,int v) 23 { 24 edge[tot].to=v; 25 edge[tot].nxt=head[u]; 26 head[u]=tot++; 27 } 28 29 void tarjan(int u) 30 { 31 int v; 32 low[u]=dfn[u]=++index; 33 stack[top++]=u; 34 vis[u]=true; 35 for ( int i=head[u];i!=-1;i=edge[i].nxt ) { 36 v=edge[i].to; 37 if ( !dfn[v] ) { 38 tarjan(v); 39 low[u]=min(low[u],low[v]); 40 } 41 else if ( vis[v] ) low[u]=min(low[u],dfn[v]); 42 } 43 if ( low[u]==dfn[u] ) { 44 scc++; 45 do { 46 v=stack[--top]; 47 vis[v]=false; 48 belong[v]=scc; 49 num[scc]++; 50 } 51 while ( v!=u ); 52 } 53 } 54 55 void solve(int N) 56 { 57 memset(dfn,0,sizeof(dfn)); 58 memset(vis,false,sizeof(vis)); 59 memset(num,0,sizeof(num)); 60 index=scc=top=0; 61 for ( int i=1;i<=N;i++ ) { 62 if ( !dfn[i] ) tarjan(i); 63 } 64 } 65 66 void init() 67 { 68 tot=0; 69 memset(head,-1,sizeof(head)); 70 } 71 72 bool judge(int mid) 73 { 74 int i,j,k,x1,x2,y1,y2; 75 init(); 76 for ( i=1;i<=n;i++ ) { 77 for ( j=i+1;j<=n;j++ ) { 78 x1=arr[i].x; 79 y1=arr[i].y; 80 x2=arr[j].x; 81 y2=arr[j].y; 82 if ( abs(x2-x1)<mid ) { 83 if ( y1==y2 ) { 84 addedge(i,j+n); 85 addedge(j,i+n); 86 addedge(i+n,j); 87 addedge(j+n,i); 88 } 89 else if ( abs(y2-y1)<mid ) { 90 if ( y2>y1 ) { 91 addedge(j,j+n); 92 addedge(i+n,i); 93 } 94 else { 95 addedge(i,i+n); 96 addedge(j+n,j); 97 } 98 } 99 else if ( abs(y2-y1)<2*mid ) { 100 if ( y2>y1 ) { 101 addedge(j,i); 102 addedge(i+n,j+n); 103 } 104 else { 105 addedge(i,j); 106 addedge(j+n,i+n); 107 } 108 } 109 } 110 } 111 } 112 solve(2*n); 113 for ( i=1;i<=n;i++ ) { 114 if ( belong[i]==belong[i+n] ) return false; 115 } 116 return true; 117 } 118 119 int main() 120 { 121 int T,i,j,k,m,x,y,z,l,r,mid; 122 scanf("%d",&T); 123 while ( T-- ) { 124 scanf("%d",&n); 125 for ( i=1;i<=n;i++ ) { 126 scanf("%d%d",&arr[i].x,&arr[i].y); 127 } 128 l=0; 129 r=inf; 130 while ( r-l>1 ) { 131 mid=(l+r)/2; 132 if ( judge(mid) ) l=mid; 133 else r=mid; 134 } 135 printf("%d ",l); 136 } 137 return 0; 138 }
小结:首先需要明确一个状态的正反两面所代表的含义(正反两面只能独立存在,不能共存),其次要弄清楚不同状态之间的关系应当如何建边,建完边后跑tarjan,当出现矛盾时即意味着条件不能满足。同时2-sat的题目往往会与二分(看到最大/最小)结合出题。