HDU1814(Peaceful Commission) 【2-SAT DFS暴力求最小字典序的模板】

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1814

题意:给出一个数n,代表有n个党派,每个党派要求派出其中一个人去参加会议,且只能派出一人。给出m,m行每行给出a, b.代表处于不同党派中的a, b也具有矛盾关系,a, b其中也只能有一位去参加会议。这2 * n个人的编号为1 ~ 2 * n,其中 2 * i 与 2 * i - 1是属于同一个党派。要求找到一个编号字典序最小的方案。

思路:

1.每个集合中只有2个元素,并且给出部分元素之间的限制关系,典型的2-sat问题。

2.找到对立点,建‘矛盾边’,用tarjan缩点,再判断对立点是否处在一个强连通分量中,若处在则无解,若都不处在就有解。这种思路在这道题是不可行的,因为题目要求最小字典序输出。

3.由于要保证最小字典序,我们可以用染色法来求解。每个人的编号我们默认 -1 ,这样就可以运用 ^1 运算来处理同一帮派中的2个人。从0号开始遍历到2 * n - 1号人,这样寻找方案就是最小字典序。

4.扩展若同一对点的编号是不连续的,例如0-5,1-9,3-4. 那么我们就要用结构体排序(结构体中2个变量分别代表同一帮派中的2个人的编号),将同一点对中小的那个点按照从小到大的顺序排序,保证遍历的时候的字典序。(加矛盾边不需要处理)

代码里的注释很详细。

 1 #include<stdio.h>
 2 #include<string.h>
 3 #define mem(a, b) memset(a, b, sizeof(a))
 4 
 5 int n, m;
 6 int head[16100], cnt;
 7 int vis[16100], sum, node[16100];
 8 
 9 struct Edge
10 {
11     int to, next;
12 }edge[20000 * 2];
13 
14 void add(int a, int b)
15 {
16     edge[++ cnt].to = b;
17     edge[cnt].next = head[a];
18     head[a] = cnt;
19 }
20 
21 void init()
22 {
23     mem(head, -1), cnt = 0;
24     mem(vis, 0);
25 }
26 
27 int dfs(int now)
28 {
29     if(vis[now ^ 1])//若帮派中另一个人已经被选择,则自己一定不会被选择,返回0 
30         return 0;
31     if(vis[now])//若自己已经被选择,则不用再dfs了,已经找过从自己出发的方案 
32         return 1;
33     node[sum ++] = now;//记录查找过程中的点是哪些 
34     vis[now] = 1;
35     for(int i = head[now]; i != -1; i = edge[i].next)//不同帮派中的矛盾关系 
36     {
37         int to = edge[i].to;
38         if(!dfs(to))//这些边代表'必须', 不能选b,就必须选择b ^ 1,若b ^ 1找不到合理方案,说明就不存在方案了,返回0 
39             return 0;
40     }
41     return 1;
42 }
43 
44 int two_sat() //返回是否存在方案 
45 {
46     for(int i = 0; i < 2 * n; i += 2) //由小到大遍历, 保证在有合理的方案的情况下的字典序 
47     {
48         if(!vis[i] && !vis[i ^ 1]) //若遍历到有帮派中2人都未被标记, 则确定之前的标记,代表是合法方案的一部分, sum清为0 
49         {
50             sum = 0;
51             if(!dfs(i))//暴力寻找的过程中发现不存在,那么该过程中被标记的点都重新处理为未被标记 
52             {
53                 while(sum)
54                     vis[node[-- sum]] = 0;
55                 if(!dfs(i ^ 1))//若帮派中另一个人也找不到合理方案,那么就不存在方案了,直接retrun 0 
56                     return 0;
57             }
58         }
59     }
60     return 1;
61 }
62 
63 int main()
64 {
65     while(scanf("%d%d", &n, &m)!=EOF)
66     {
67         init();
68         for(int i = 1; i <= m; i ++)
69         {
70             int a, b;
71             scanf("%d%d", &a, &b);
72             a --, b --;  //形成 ^1 运算关系
73             add(a, b ^ 1), add(b, a ^ 1);
74         }
75         if(two_sat())
76         {
77             for(int i = 0; i < 2 * n; i += 2)
78             {
79                 if(vis[i])
80                     printf("%d\n", i + 1);
81                 else
82                     printf("%d\n", (i ^ 1) + 1);
83             }
84         }
85         else
86             printf("NIE\n");
87     }
88     return 0;
89 }
View Code

 

posted @ 2019-05-24 11:32  缘未到  阅读(117)  评论(0编辑  收藏  举报