初涉2-SAT

2-SAT:有趣的图论模型

什么是2-SAT

SAT是适定性(Satisfiability)问题的简称。之所以研究2-sat是因为当k>2时,k-sat问题已经被证明是NPC的了。

2-sat问题简单来说就是有$n$个二元集合,其中一些元素不能同时取到,问每个集合各取一个的方案。

至于2-sat的算法,就是将不能同时取元素的限制转为有向边,再跑一遍tarjan(如果要求判断可行性);或者dfs判断是否冲突(求方案)。

难就难在建模吧……或者说其他难点我还没学会?

这里有一篇论文2-sat问题和一篇博客【研究总结】2-sat问题,先攒着吧……

一些例题

【小技巧转化】bzoj1823: [JSOI2010]满汉全席

Description

满汉全席是中国最丰盛的宴客菜肴,有许多种不同的材料透过满族或是汉族的料理方式,呈现在數量繁多的菜色之中。由于菜色众多而繁杂,只有极少數博学多闻技艺高超的厨师能够做出满汉全席,而能够烹饪出经过专家认证的满汉全席,也是中国厨师最大的荣誉之一。 世界满汉全席协会是由能够料理满汉全席的专家厨师们所组成,而他们之间还细分为许多不同等级的厨师。为了招收新进的厨师进入世界满汉全席协会,将于近日举办满汉全席大赛,协会派遣许多会员当作评审员,为的就是要在參赛的厨师之中,找到满汉料理界的明日之星。 大会的规则如下:每位參赛的选手可以得到n 种材料,选手可以自由选择用满式或是汉式料理将材料当成菜肴。大会的评审制度是:共有m 位评审员分别把关。每一位评审员对于满汉全席有各自独特的見解,但基本见解是,要有兩样菜色作为满汉全席的标志。如某评审认为,如果没有汉式东坡肉跟满式的涮羊肉锅,就不能算是满汉全席。但避免过于有主見的审核,大会规定一个评审员除非是在认为必备的两样菜色都没有做出來的狀况下,才能淘汰一位选手,否则不能淘汰一位參赛者。换句话說,只要參赛者能在这兩种材料的做法中,其中一个符合评审的喜好即可通过该评审的审查。如材料有猪肉,羊肉和牛肉时,有四位评审员的喜好如下表: 评审一 评审二 评审三 评审四 满式牛肉 满式猪肉 汉式牛肉 汉式牛肉 汉式猪肉 满式羊肉 汉式猪肉 满式羊肉 如參赛者甲做出满式猪肉,满式羊肉和满式牛肉料理,他将无法满足评审三的要求,无法通过评审。而參赛者乙做出汉式猪肉,满式羊肉和满式牛肉料理,就可以满足所有评审的要求。 但大会后來发现,在这样的制度下如果材料选择跟派出的评审员没有特别安排好的话,所有的參赛者最多只能通过部分评审员的审查而不是全部,所以可能会发生没有人通过考核的情形。如有四个评审员喜好如下表时,则不論參赛者采取什么样的做法,都不可能通过所有评审的考核: 评审一 评审二 评审三 评审四 满式羊肉 满式猪肉 汉式羊肉 汉式羊肉 汉式猪肉 满式羊肉 汉式猪肉 满式猪肉 所以大会希望有人能写一个程序來判断,所选出的m 位评审,会不会发生 没有人能通过考核的窘境,以便协会组织合适的评审团。

Input

第一行包含一个数字 K,代表测试文件包含了K 组资料。每一组测试资料的第一行包含兩个数字n 跟m(n≤100,m≤1000),代表有n 种材料,m 位评审员。为方便起見,材料舍弃中文名称而给予编号,编号分别从1 到n。接下來的m 行,每行都代表对应的评审员所拥有的兩个喜好,每个喜好由一个英文字母跟一个数字代表,如m1 代表这个评审喜欢第1 个材料透过满式料理做出來的菜,而h2 代表这个评审员喜欢第2 个材料透过汉式料理做出來的菜。每个测试文件不会有超过50 组测试资料

Output

每笔测试资料输出一行,如果不会发生没有人能通过考核的窘境,输出GOOD;否则输出BAD(大写字母)。


题目分析

感觉那年出题人……很亦可赛艇啊。

这里的二元组限制变成了必须选一种,但是实际上转化一下就是一样的。

例如m3和h1必须选一种,那么等价于选m3必选h1;选m1必选m3。于是直接用tarjan判断可行性就好了。

 

 1 /**************************************************************
 2     Problem: 1823
 3     User: AntiQuality
 4     Language: C++
 5     Result: Accepted
 6     Time:68 ms
 7     Memory:1324 kb
 8 ****************************************************************/
 9  
10 #include<bits/stdc++.h>
11 const int maxn = 403;
12 const int maxm = 3303;
13  
14 int tt,n,m;
15 int tim,dfn[maxn],low[maxn];
16 int stk[maxn],cnt,col[maxn],cols;
17 int edgeTot,edges[maxm],nxt[maxm],head[maxn];
18  
19 int read()
20 {
21     char ch = getchar();
22     int num = 0, fl;
23     for (; !isalpha(ch); ch = getchar());
24     fl = (ch=='h'?0:n);
25     for (; !isdigit(ch); ch = getchar());
26     for (; isdigit(ch); ch = getchar())
27         num = (num<<1)+(num<<3)+ch-48;
28     return num+fl;
29 }
30 inline int ene(int x){return x>n?x-n:x+n;}
31 void addedge(int u, int v)
32 {
33     edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
34 }
35 void tarjan(int u)
36 {
37     dfn[u] = low[u] = ++tim;
38     stk[++cnt] = u;
39     for (int i=head[u]; i!=-1; i=nxt[i])
40     {
41         int v = edges[i];
42         if (!dfn[v])
43             tarjan(v), low[u] = std::min(low[u], low[v]);
44         else if (!col[v])
45             low[u] = std::min(low[u], dfn[v]);
46     }
47     if (dfn[u]==low[u]){
48         col[u] = ++cols;
49         for (; stk[cnt]!=u; cnt--)
50             col[stk[cnt]] = cols;
51         cnt--;
52     }
53 }
54 int main()
55 {
56     scanf("%d",&tt);
57     while (tt--)
58     {
59         edgeTot = cnt = cols = tim = 0;
60         memset(head, -1, sizeof head);
61         memset(dfn, 0, sizeof dfn);
62         memset(col, 0, sizeof col);
63         scanf("%d%d",&n,&m);
64         for (int i=1; i<=m; i++)
65         {
66             int u = read(), v = read();
67             addedge(ene(u), v);
68             addedge(ene(v), u);
69         }
70         for (int i=1; i<=2*n; i++)
71             if (!dfn[i]) tarjan(i);
72         bool fl = 1;
73         for (int i=1; i<=n; i++)
74             if (col[i]==col[i+n]){
75                 fl = 0;
76                 break;
77             }
78         if (fl) puts("GOOD");
79         else puts("BAD");
80     }
81     return 0;
82 }

 

hdu1814Peaceful Commission

Problem Description

The Public Peace Commission should be legislated in Parliament of The Democratic Republic of Byteland according to The Very Important Law. Unfortunately one of the obstacles is the fact that some deputies do not get on with some others. 

The Commission has to fulfill the following conditions: 
1.Each party has exactly one representative in the Commission, 
2.If two deputies do not like each other, they cannot both belong to the Commission. 

Each party has exactly two deputies in the Parliament. All of them are numbered from 1 to 2n. Deputies with numbers 2i-1 and 2i belong to the i-th party . 

Task 

Write a program, which: 
1.reads from the text file SPO.IN the number of parties and the pairs of deputies that are not on friendly terms, 
2.decides whether it is possible to establish the Commission, and if so, proposes the list of members, 
3.writes the result in the text file SPO.OUT. 

Input

In the first line of the text file SPO.IN there are two non-negative integers n and m. They denote respectively: the number of parties, 1 <= n <= 8000, and the number of pairs of deputies, who do not like each other, 0 <= m <=2 0000. In each of the following m lines there is written one pair of integers a and b, 1 <= a < b <= 2n, separated by a single space. It means that the deputies a and b do not like each other. 
There are multiple test cases. Process to end of file. 

Output

The text file SPO.OUT should contain one word NIE (means NO in Polish), if the setting up of the Commission is impossible. In case when setting up of the Commission is possible the file SPO.OUT should contain n integers from the interval from 1 to 2n, written in the ascending order, indicating numbers of deputies who can form the Commission. Each of these numbers should be written in a separate line. If the Commission can be formed in various ways, your program may write mininum number sequence. 

题目大意

有n个党派,每个党派只有2人,现要设立一个和平委员会,必须满足如下条件;

  • 每个党派在委员会中恰有一个代表
  • 如果两个代表互相反感,那么他们都不能属于委员会。

求可行性以及字典序最小方案

题目分析

嘛……最早我做的时候第一问用的tarjan缩点,第二问瞎搞:对于每一个入度为零的点先做一遍dfs以确定它之后以及它自己是否合法。

但是可能思路有点乱写挂了好久……花了好几个小时都没弄出来。

后来在网上发现一种比较好的思路:其实就是最上面提到的dfs方法。显而易见dfs过程中可以判断可行性,那么为什么还要去写个没什么其他用处的tarjan呢?

这里的dfs跟我最早想的dfs也一样(所以不清楚我到底哪里写挂),通过子节点合法性回溯回查找节点。不过这样总的来说就可以按照顺序进行而不用考虑乱七八糟的东西了。

 

1 bool dfs(int x)
2 {
3     if (vis[ene(x)]) return 0;
4     if (vis[x]) return 1;
5     vis[x] = 1, stk[++cnt] = x;
6     for (int i=head[x]; i!=-1; i=nxt[i])
7         if (!dfs(edges[i])) return 0;
8     return 1;
9 }

 

这里是dfs判断的代码。

所以其实是60来行就可以搞定的事情。

 1 #include<bits/stdc++.h>
 2 const int maxn = 20035;
 3 const int maxm = 50035;
 4 
 5 int n,m;
 6 int edges[maxm],nxt[maxm],head[maxn],edgeTot;
 7 int stk[maxn],cnt;
 8 bool vis[maxn];
 9 
10 int read()
11 {
12     char ch = getchar();
13     int num = 0;
14     bool fl = 0;
15     for (; !isdigit(ch); ch = getchar())
16         if (ch=='-') fl = 1;
17     for (; isdigit(ch); ch = getchar())
18         num = (num<<1)+(num<<3)+ch-48;
19     if (fl) num = -num;
20     return num;
21 }
22 inline int ene(int x){return ((x-1)^1)+1;}
23 void addedge(int u, int v)
24 {
25     edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
26 }
27 bool dfs(int x)
28 {
29     if (vis[ene(x)]) return 0;
30     if (vis[x]) return 1;
31     vis[x] = 1, stk[++cnt] = x;
32     for (int i=head[x]; i!=-1; i=nxt[i])
33         if (!dfs(edges[i])) return 0;
34     return 1;
35 }
36 int main()
37 {
38     while (scanf("%d%d",&n,&m)!=EOF&&n&&m)
39     {
40         memset(vis, 0, sizeof vis);
41         memset(head, -1, sizeof head);
42         cnt = edgeTot = 0;
43         for (int i=1; i<=m; i++)
44         {
45             int x = read(), y = read();
46             addedge(x, ene(y)), addedge(y, ene(x));
47         }
48         bool fl = 1;
49         for (int i=1; i<=n; i++)
50             if (!vis[2*i]&&!vis[2*i-1]){
51                 cnt = 0;
52                 if (!dfs(2*i-1)){
53                     while (cnt) vis[stk[cnt]] = 0, cnt--;
54                     if (!dfs(2*i)){
55                         fl = 0;
56                         break;
57                     }
58                 }
59             }
60         if (fl){
61             for (int i=1; i<=2*n; i++)
62                 if (vis[i]) printf("%d\n",i);
63         }else puts("NIE");
64     }
65     return 0;
66 }

 

 

END

posted @ 2018-07-05 10:00  AntiQuality  阅读(181)  评论(0编辑  收藏  举报