acwing----二分图与匈牙利算法
一.什么是二分图?:
引用了这个大佬的话:https://www.acwing.com/solution/content/105874/
有两顶点集且图中每条边的的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接!
说人话的定义:图中点通过移动能分成左右两部分,左侧的点只和右侧的点相连,右侧的点只和左侧的点相连。
下图就是个二分图:

如果判断一个图是不是二分图?
开始对任意一未染色的顶点染色。
判断其相邻的顶点中,若未染色则将其染上和相邻顶点不同的颜色。
若已经染色且颜色和相邻顶点的颜色相同则说明不是二分图,若颜色不同则继续判断。
bfs和dfs可以搞定!
具体例子:
1 给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。
2
3 请你判断这个图是否是二分图。
4
5 输入格式
6 第一行包含两个整数 n 和 m。
7
8 接下来 m 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。
9
10 输出格式
11 如果给定图是二分图,则输出 Yes,否则输出 No。
12
13 数据范围
14 1≤n,m≤10^5
16 输入样例:
17 4 4
18 1 3
19 1 4
20 2 3
21 2 4
22 输出样例:
23 Yes
1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 const int N = 1e5 + 10, M = 2 * 1e5 + 10; 6 int e[M], ne[M], h[N], idx = 0; //链表保存; 7 /* int st[N] */ //其实不用st[N],因为color[N]可以计入; 8 int color[N]; 9 void add(int a, int b) 10 { 11 e[idx] = b, ne[idx] = h[a], h[a] = idx++; 12 } 13 bool dfs(int x, int num) // dfs的作用是将x点染色,并把它能到达的点染成不同颜色; 14 {//x代表正在看的点,num表示要染成的颜色: 15 color[x] = num; 16 for (int i = h[x]; i != -1; i = ne[i]) 17 { 18 int j = e[i]; 19 if (!color[j]) 20 { 21 if (!dfs(j, 3 - num))//当前这个点染成1,那邻接的点染成2,正好可以用3-1表示,3-2同理: 22 return false; 23 } 24 else if (color[j] == num) //这说明有相邻的点的颜色相同,即不是二分图 25 return false; 26 } 27 return true; 28 } 29 int main() 30 { 31 memset(h, -1, sizeof(h)); 32 int n, m; 33 scanf("%d%d", &n, &m); 34 while (m--) 35 { 36 int a, b; 37 scanf("%d%d", &a, &b); 38 add(a, b), add(b, a);//因为是无向图,所以要保存一下两边 39 } 40 bool flag = true; 41 for (int i = 1; i <= n; i++) 42 { 43 if (!color[i])//即当这个点没有看过 44 { 45 if (!dfs(i, 1)) 46 { 47 flag = false; 48 break; 49 } 50 } 51 } 52 if (flag) 53 printf("Yes\n"); 54 else 55 printf("No\n"); 56 return 0; 57 }
二. 二分图的应用--匈牙利算法
时间复杂度O(n*m)n 为点数,m为边数
二分图的最大匹配:
接下来我将用y总的“找妹子”的思想表达一下算法的想法:

如图,我将两个点集分别设为 男,女,他们之间有如上的关系(用线连起来了),用线连起来的男女表示相互之间有好感,
现在我的任务是月老,要最大的使男女之间形成恋爱关系,(当然一定是一男一女才是配对了,不能一对多或多对一)如何作呢?:
以这个图为例:我们只看男生:
第一个男生 (一)他的第一个在意的女生是(2),且这个女生没有与其他男生建立关系,那么我就将他们匹配。
第一个男生匹配完成,接下来看下一个男生(二),他在意的第一个女生是(1),这个女生也没有与其他男生建立关系,匹配;
第三个男生(三),发现他喜欢的第一个女生(2)以及匹配了,关键来了:
这个时候,男生(三)不会善罢甘休,他会找到与女生(2)匹配的男生(一)商谈一下,看一下男生(一)能否与其他女生去匹配,把女生(2)让给男生(三)。
巧了,男生(一)正好可以与另一个女生(4)匹配,那么这时男生(三)就可以与女生(2)在一起了。
然后男生(四)与女生(3)在一起。
匹配结束,最大匹配为4;
下面给出具体例子:
1 给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。 2 3 数据保证任意一条边的两个端点都不可能在同一部分中。 4 5 请你求出二分图的最大匹配数。 6 7 二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。 8 9 二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。 10 11 输入格式 12 第一行包含三个整数 n1、 n2 和 m。 13 14 接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。 15 16 输出格式 17 输出一个整数,表示二分图的最大匹配数。 18 19 数据范围 20 1≤n1,n2≤500, 21 1≤u≤n1, 22 1≤v≤n2, 23 1≤m≤105 24 输入样例: 25 2 2 4 26 1 1 27 1 2 28 2 1 29 2 2 30 输出样例: 31 2
1 #include <iostream> 2 #include <algorithm> 3 #include <cstring> 4 using namespace std; 5 const int N = 500 + 10, M = 1e5 + 10; 6 int e[M], ne[M], h[N], idx = 0; 7 int match[N]; //表示女生i,与男生match[i]匹配; 8 bool st[N]; //在一次男生找女生的过程中记录一下这个女生是否以及被找过; 9 void add(int a, int b) 10 { 11 e[idx] = b, ne[idx] = h[a], h[a] = idx++; 12 } 13 bool find(int x) 14 { 15 for (int i = h[x]; i != -1; i = ne[i]) 16 { 17 int j = e[i]; 18 if (!st[j]) 19 { 20 st[j]=true; 21 if (match[j] == 0 || find(match[j]))//这里如果match[j]!=0说明这个女生有男生了
//find(match[j])是为了看一下,match[j]这个男生能否找其他女生,用st[j]记录一下找过女生j了,当match[j]再找时不能找这个女生了 22 { 23 match[j] = x; 24 return true; 25 } 26 } 27 } 28 return false; 29 } 30 int main() 31 { 32 int n1, n2, m; 33 scanf("%d%d%d", &n1, &n2, &m); 34 memset(h,-1,sizeof(h)); 35 while (m--) 36 { 37 int a, b; 38 scanf("%d%d", &a, &b); 39 add(a, b); //只要记录一下男生到女生的关系就可以; 40 } 41 int ans = 0; 42 for (int i = 1; i <= n1; i++) 43 { 44 memset(st, false, sizeof(st));//必须每一次都初始化为false,这个妹子有男朋友,并不影响当前这个男生去尝试 45 if (find(i)) 46 ans++; 47 } 48 printf("%d", ans); 49 return 0; 50 }
对st[N]的理解有一位网友的:https://www.acwing.com/user/myspace/index/49357/
disheng
对于st数组的理解:1.必须要存在,不存在就会出现find无限递归同一个女孩;2.必须每次要全部更新为false,否则男孩无法挖墙脚,从而会得不到最大匹配 (欢迎纠正)
《二分图染色判断》


从图的角度看:
从集合的角度看:
如果染色失败,即有边的相邻点是同一种颜色,这种情况只有当图中出现奇数环的情况才会出现(奇数环即环的点数是奇数)
上述就是二分图染色的原理,具体运用:257. 关押罪犯


通过题目描述可知:我们要将冲突大的罪犯尽量分开,十分符合二分图的特点
我们可以通过二分求出最大值mid,然后通过二分图染色,即将边(冲突)权值大于mid的两个点分开,
通过是否能够将全部边(冲突)权值大于mid的两个点分开(即二分图染色是否可以成功)判断这个最大值是否可行;
1 #include <iostream>
2 #include <algorithm>
3 #include <vector>
4 #include <cstring>
5 using namespace std;
6 typedef pair<int, int> PII;
7 const int N = 2 * 1e4 + 5, M = 1e5 + 5;
8 int n, m;
9 vector<PII> sides[N];
10 //-1表示染色为黑色,1表示染色为白色,0表示还没有染色
11 int color[N];
12 bool dfs(int x, int s, int mid)
13 {
14 color[x] = s;
15 for (int i = 0; i < sides[x].size(); i++)
16 {
17 int to = sides[x][i].first, w = sides[x][i].second;
18 // 判断是否大于mid的都可以被分开
19 if (w > mid)
20 {
21 if (color[to] == 0)
22 {
23 if (!dfs(to, -s, mid))
24 return false;
25 }
26 else if (color[to] == s)
27 return false;
28 }
29 }
30 return true;
31 }
32 bool check(int x)
33 {
34 memset(color, 0, sizeof(color));
35 for (int i = 1; i <= n; i++)
36 {
37 if (color[i] == 0)
38 if (!dfs(i, -1, x))
39 return false;
40 }
41 return true;
42 }
43 int main()
44 {
45 cin >> n >> m;
46 for (int i = 1; i <= m; i++)
47 {
48 int a, b, c;
49 scanf("%d%d%d", &a, &b, &c);
50 sides[a].push_back({b, c}), sides[b].push_back({a, c});
51 }
52 int l = 0, r = 1e9, ans;
53 while (l <= r)
54 {
55 int mid = l + r >> 1;
56 if (check(mid))
57 {
58 r = mid - 1;
59 ans = mid;
60 }
61 else
62 l = mid + 1;
63 }
64 cout << ans;
65 return 0;
66 }
《最大匹配的运用》
首先在二分图中有结论:二分图最大匹配时,不存在增广路径
所谓增广路径是什么?

这个P就是我们说的增广路径,
M表示已经匹配好的两个点之间的边,在写代码的时候我们都将M设为变量match[]

当前已有边(1,1')和(4,3')属于M

然后我们将属于M的路径与不属于M的路径取反,即如下:

发现会有比原先更大匹配数的出现
运用:匈牙利算法
这个算法中的find()函数即是用来找增广路径并且进行M反转,从而找到最大匹配数的算法


同时我们发现规律,上面的图中,白格子的x+y必定是偶数,染色的格子x+y必定是奇数
1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 using namespace std;
5 typedef pair<int, int> PII;
6 const int N = 101;
7 // 记录不能放置格子的位置;
8 int g[N][N], n, t;
9 // 记录黑格子匹配的白格子是(x,y);
10 PII match[N][N];
11 // 看在一次匹配中黑格子是否被匹配;
12 bool st[N][N];
13 int dy[] = {0, 1, 0, -1}, dx[] = {-1, 0, 1, 0};
14 bool find(int x, int y)
15 {
16 for (int i = 0; i < 4; i++)
17 {
18 // 枚举白格子身边的每一个黑格子
19 int nx = x + dx[i], ny = y + dy[i];
20 if (nx > 0 && nx <= n && ny > 0 && ny <= n && g[nx][ny] == 0 && !st[nx][ny])
21 {
22 st[nx][ny] = true;
23 if ((match[nx][ny].first == 0 && match[nx][ny].second == 0) ||
24 find(match[nx][ny].first, match[nx][ny].second))
25 {
26 match[nx][ny] = {x, y};
27 return true;
28 }
29 }
30 }
31 return false;
32 }
33 int main()
34 {
35 cin >> n >> t;
36 for (int i = 1; i <= t; i++)
37 {
38 // 第 x 行第 y 列
39 int x, y;
40 scanf("%d%d", &x, &y);
41 g[x][y] = 1;
42 }
43 int ans = 0;
44 for (int i = 1; i <= n; i++)
45 for (int j = 1; j <= n; j++)
46 {
47 // 这里是枚举每一个白格子,将其一个一个匹配;
48 memset(st, false, sizeof(st));
49 if ((i + j) % 2 == 0 && g[i][j] == 0)
50 if (find(i, j))
51 ans++;
52 }
53 cout << ans;
54 return 0;
55 }

浙公网安备 33010602011771号