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 }
View Code

 

posted @ 2019-05-17 16:10  缘未到  阅读(127)  评论(0编辑  收藏  举报