二分图

二分图又称作二部图,是图论中的一种特殊模型。 设$G=(V,E)$是一个无向图,如果顶点$V$可分割为两个互不相交的子集$(A,B)$,并且图中的每条边$(i,j)$所关联的两个顶点$i$和$j$分别属于这两个不同的顶点集$(i  \ in \  A,j \  in  \ B)$,则称图$G$为一个二分图。

1.二分图的判定:不存在奇数环或染色法不生产矛盾。

关押罪犯

 

 

 二分 + 二分图判定。

对于一个二分点$mid$,如果仇恨值大于$mid$的所有罪犯在二分图的两个集合内(集合内部没有边),$mid$就是合法的。$mid$是合法的,那么大于$mid$的所有点能构成二分图,当$mid$取更大值的时候也一定能构成二分图(二分图去掉一些边还是二分图)。所以只需要判断大于mid的关系是否构成二分图。

如果$check(mid)$成功,说明$mid$可行,大于$mid$的也可行,答案在左边,$r = mid$,反之$l = mid + 1$。

本题的边界0不需要特判,因为$mid = 0$的时候,也是满足二分图的判定条件。

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 using namespace std;
 7 
 8 const int N = 20010, M = 200010;
 9 
10 int n, m;
11 int h[N], e[M], w[M], ne[M], idx;
12 int color[N];
13 
14 void add(int a, int b, int c)
15 {
16     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
17 }
18 
19 
20 bool dfs(int u, int c, int mid)
21 {
22     color[u] = c;
23     for(int i = h[u] ; ~i ; i = ne[i])
24     {
25         int j = e[i];
26         if(w[i] <= mid)continue;//小于mid的边就不用管
27         if(color[j])
28         {
29             if(color[j] == c)return false;
30         }
31         else if(!dfs(j, 3 - c, mid))return false;
32     }
33     return true;
34 }
35 
36 bool check(int mid)
37 {
38     memset(color, 0, sizeof color);
39     for (int i = 1; i <= n; i ++ )
40         if (!color[i])//未染色就染色
41             if (!dfs(i, 1, mid))
42                 return false;
43     return true;
44 }
45 
46 int main()
47 {
48     scanf("%d%d", &n, &m);
49     memset(h, -1, sizeof h);
50 
51     while (m -- )
52     {
53         int a, b, c;
54         scanf("%d%d%d", &a, &b, &c);
55         add(a, b, c), add(b, a, c);
56     }
57 
58     int l = 0, r = 1e9;
59     while (l < r)
60     {
61         int mid = l + r >> 1;
62         if (check(mid)) r = mid;
63         else l = mid + 1;
64     }
65 
66     printf("%d\n", r);
67 
68     return 0;
69 }

 

 

 

 

 二分图的最大匹配

匈牙利算法:

①匹配:对于二分图,匹配指一组边,每两条边之间没有公共点。

②最大匹配:边数最多的匹配。

③ 匹配点:匹配中的点。

④非匹配点:不在匹配当中的点。

⑤增广路径:从非匹配点->非匹配边->匹配边->非匹配边->匹配边->非匹配边->非匹配点。从非匹配点走,最后到非匹配点。

最大匹配 <->不存在增广路径

棋盘覆盖

可以将棋盘的每一个格子看做一个点,相邻的格子看做一条边,问题就变成了最多选多少条边,使得边没有公共点, 即找最大匹配。将格子的横纵坐标相加的值为奇数或偶数分为两边,可以得知是一个二分图,题意就变成求二分图的最大匹配问题。

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 #define x first
 7 #define y second
 8 
 9 using namespace std;
10 
11 typedef pair<int, int> PII;
12 
13 const int N = 110;
14 
15 bool g[N][N], st[N][N];
16 PII match[N][N];
17 int n, m;
18 int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
19 
20 
21 bool find(int x, int y)
22 {
23     for(int i = 0 ; i < 4 ; i ++)
24     {
25         int a = x + dx[i], b = y + dy[i];
26         if(a < 1 || b < 1 || a > n || b > n)continue;
27         if(st[a][b] || g[a][b])continue;
28         st[a][b] = true;
29         PII t = match[a][b];
30         if(t.x == -1 || find(t.x, t.y))
31         {
32             match[a][b] = {x, y};
33             return true;
34         }
35     }
36     return false;
37 }
38 
39 int main(){
40     scanf("%d%d", &n, &m);
41     
42     while(m --)
43     {
44         int x, y;
45         scanf("%d%d", &x, &y);
46         g[x][y] = true;
47     }
48     
49     memset(match, -1, sizeof match);
50     
51     int res = 0;
52     for(int i = 1 ; i <= n ; i ++)
53         for(int j = 1 ; j <= n ; j ++)
54             if((j + i) % 2 && !g[i][j])//奇数点偶数点对称,选一种就可以
55             {
56                 memset(st, 0, sizeof st);
57                 if(find(i, j))res ++;//能找到匹配就加上
58             }
59     
60     printf("%d\n", res);
61     return 0;
62 }

 

 

 

 

最小点覆盖:每一条边至少有一个点被选出,选出的点数最少

在二分图中,最小点覆盖 = 最大匹配数。

机器任务

 

 可以将题目中的a[i]向b[i]连一条边,转换成,a[i]到b[i]的边上至少要有一个被选点,求最小点覆盖。

代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 using namespace std;
 7 
 8 const int N = 110;
 9 int match[N];
10 bool st[N], g[N][N];
11 int n, m, k;
12 
13 bool find(int x)
14 {
15     for(int i = 1 ; i < m ; i ++)
16         if(!st[i] && g[x][i])
17         {
18             st[i] = true;
19             if(match[i] == -1 || find(match[i]))
20             {
21                 match[i] = x;
22                 return true;
23             }
24         }
25     return false;
26 }
27 
28 int main(){
29     while(cin >> n, n)
30     {
31         cin >> m >> k;
32         memset(match, -1, sizeof match);
33         memset(g, 0, sizeof g);
34         
35         while(k --)
36         {
37             int t, a, b;
38             cin >> t >> a >> b;
39             if(!a || !b)continue;//刚开始是0就直接完成,无需记录
40             g[a][b] = true;
41         }
42         
43         int res = 0;
44         for(int i = 1 ; i < n ; i ++)
45         {
46             memset(st, 0, sizeof st);
47             if(find(i))res ++;
48         }
49         
50         
51         cout << res << endl;
52     }
53     
54  
55     return 0;
56 }

 

 

 

 

最大独立集:从图中选最多的点,使得选出的点之间(作为一个集合内部)无边。

最大团:从图中选最多的点,使得选出的点之间(作为一个集合内部)都有边。

在二分图中 求最大独立集<->先求去掉最少的点,将所有边破坏<->先求最小点覆盖<->先求最大匹配。

所以最大独立集 = 总数 - 最大匹配

 

骑士放置

这题也可以用状压DP做。

将每个格子看做一个点,关系看做一条边,题意就变成选取最多的点,使每个点之间无边,格子的总数减去最大匹配和不能填的点数,即为答案。

 代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <cstdio>
 5 
 6 #define x first
 7 #define y second
 8 
 9 using namespace std;
10 
11 typedef pair<int,int> PII;
12 
13 const int N = 110;
14 
15 PII match[N][N];
16 bool st[N][N], g[N][N];
17 int n, m, k;
18 
19 int dx[8] = {-1, 1, 2, 2, 1, -1, -2, -2};
20 int dy[8] = {2, 2, 1, -1, -2, -2, -1, 1};
21 
22 bool find(int x, int y)
23 {
24     for(int i = 0 ; i < 8 ; i ++)
25     {
26         int a = x + dx[i], b = y + dy[i];
27         if(a < 1 || b < 1 || a > n || b > m)continue;
28         if(g[a][b] || st[a][b])continue;
29         st[a][b] = true;
30         PII t = match[a][b];
31         if(t.x == 0 || find(t.x, t.y))
32         {
33             match[a][b] = {x, y};
34             return true;
35         }
36     }
37     return false;
38 }
39 
40 int main(){
41     cin >> n >> m >> k;
42     
43     for(int i = 0 ; i < k ; i ++)//下面需要用到k,不能用while(k --)
44     {
45         int x, y;
46         cin >> x >> y;
47         g[x][y] = true;
48     }
49     
50     int res = 0;
51     for(int i = 1 ; i <= n ; i ++)
52         for(int j = 1 ; j <= m ; j ++)
53         if((i + j) % 2 && !g[i][j])
54         {
55             memset(st, 0, sizeof st);
56             if(find(i, j))res ++;
57         }
58     
59     cout << n * m - k - res  << endl;
60     return 0;
61 }

 

二分图的最小路径点覆盖:用最少的互不相交的路径将所有点覆盖

最小路径点覆盖 = 总点数- 最大匹配数。

最小路径重复点覆盖:用最少的边将所有点覆盖,可以相交,可以重边。

①先做一次传递闭包。

②做最小路径点覆盖。

 

posted @ 2019-12-05 21:29  dzcixy  阅读(146)  评论(0)    收藏  举报