POJ 3207 【2-SAT入门题 + 强连通分量】
这道题是我对于2-SAT问题的入门题:http://poj.org/problem?id=3207
一篇非常非常非常好的博客,很详细,认真看一遍差不多可以了解个大概:https://blog.csdn.net/JarjingX/article/details/8521690
总结一下我对于 2-SAT 问题的初步见解:
有很多个集合,每个集合里面有若干元素,现给出一些取元素的规则,要你判断是否可行,可行则给出一个可行方案。如果所有集合中,元素个数最多的集合有k个,那么我们就说这是一个k-sat问题,同理,2-sat问题就是k=2时的情况。
通常我们需要将集合中点之间的边转化成点, 再将点转化成多条新边, 这些新边就要看问题是属于哪类模型。
模型一:两者(A,B)不能同时取
那么选择了A就只能选择B’,选择了B就只能选择A’
连边A→B’,B→A’
模型二:两者(A,B)不能同时不取
那么选择了A’就只能选择B,选择了B’就只能选择A
连边A’→B,B’→A
模型三:两者(A,B)要么都取,要么都不取
那么选择了A,就只能选择B,选择了B就只能选择A,选择了A’就只能选择B’,选择了B’就只能选择A’
连边A→B,B→A,A’→B’,B’→A’
模型四:两者(A,A’)必取A
连边A’→A
题目大意:
一个圆环上有n个点, 点的序号从 0 到 n - 1, 给出m条边,说明点a, b之间有一条边相连接, 这条边可以在圆内部,也可以在圆的外部, 但边与边之间不能相交。求是否有满足的情况。
解题思路:
设对于一条边,在圆内部连接为i,圆外部连接为 i',那么两条理论上相交的边i, j(通过两条边的左右坐标来判断是否相交),就不能同时选择在圆的同一边。也就是模型一:两者(A, B)不能同时选。
注意的是 i'只是区别于i的一条对立边, 但是这条对立边不能影响到原来所存在的边, 这里我们可以用 i + m来表示 i 的对立边。
所以我们加边为 ,add(i,j + m), add(i + m, j), add(j, i + m), add(j + m, i)
然后再对 2 * m 个点(包括对立点)进行tarjan, 最后判断 m 个原始边的对立边是否处在同一个强连通分量中, 若存在处在同一个强连通分量中的,则无解,否则有解。
代码:
1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #include<stack> 5 #define mem(a, b) memset(a, b, sizeof(a)) 6 using namespace std; 7 8 int n, m; //n个点, m条边 ,点是从 0 到 n - 1 9 int l[1100], r[1100]; 10 int cnt, head[1100]; 11 int dfn[1100], low[1100], vis[1100], deep, color, belong[1100]; 12 stack<int>S; 13 14 void init() 15 { 16 mem(head, -1); 17 cnt = 0; 18 mem(dfn, 0); 19 mem(low, 0); 20 mem(vis, 0); 21 mem(belong, 0); 22 deep = 0; 23 color = 0; 24 } 25 26 struct Edge 27 { 28 int to, next; 29 int from; 30 }edge[500 * 500 * 4]; //要注意这个边的数量 31 32 void add(int a, int b) 33 { 34 edge[++ cnt].to = b; 35 edge[cnt].next = head[a]; 36 head[a] = cnt; 37 } 38 39 void tarjan(int now) 40 { 41 dfn[now] = low[now] = ++ deep; 42 vis[now] = 1; 43 S.push(now); 44 for(int i = head[now]; i != -1; i = edge[i].next) 45 { 46 int to = edge[i].to; 47 if(!dfn[to]) 48 { 49 tarjan(to); 50 low[now] = min(low[now], low[to]); 51 } 52 else if(vis[to]) 53 low[now] = min(low[now], dfn[to]); 54 } 55 if(dfn[now] == low[now]) 56 { 57 color ++; 58 while(1) 59 { 60 int temp = S.top(); 61 S.pop(); 62 vis[temp] = 0; 63 belong[temp] = color; 64 if(temp == now) 65 break; 66 } 67 } 68 } 69 70 int main() 71 { 72 int flag = 1; 73 init(); 74 scanf("%d%d", &n, &m); 75 for(int i = 1; i <= m; i ++) 76 { 77 int a, b; 78 scanf("%d%d", &a, &b); 79 if(a > b) 80 swap(a, b); 81 l[i] = a, r[i] = b;//记录每条边的左端点和右端点,保证左小右大 82 } 83 for(int i = 1; i < m; i ++) //建 2-SAT 图,将边转化成点处理 84 { // A B不能同时存在的模型 85 for(int j = i + 1; j <= m; j ++) //理论上有相交的边就转化成点来构造 2-SAT 问题的边 86 { 87 if(l[i] <= l[j] && r[i] >= l[j] && r[i] <= r[j] || l[i] >= l[j] && r[i] >= r[j] && l[i] <= r[j]) 88 { 89 add(i, j + m); 90 add(i + m, j); 91 add(j, i + m); 92 add(j + m, i); 93 } 94 } 95 } 96 for(int i = 1; i <= 2 * m; i ++)//对于 i 边,其对立边为 i + m.每条边被看成点, 即总共有 2 * m个点 97 if(!dfn[i]) 98 tarjan(i); 99 for(int i = 1; i <= m; i ++)//点与对立点不能同时存在一个强连通分量中, 否则问题无解 100 { 101 int x = belong[i], y = belong[i + m]; 102 if(x == y) 103 { 104 flag = 0; 105 break; 106 } 107 } 108 if(flag) 109 printf("panda is telling the truth...\n"); 110 else 111 printf("the evil panda is lying again\n"); 112 return 0; 113 }