搜索与图论(三)
Prim
858. Prim算法求最小生成树
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
数据范围
图中涉及边的边权的绝对值均不超过 10000。
输入样例:
1 | 4 5 |
输出样例:
1 | 6 |
AC
思路
S: 当前已经在联通块中的所有点的集合
- dist[i] = inf
- for n 次
t<-S 外离 S 最近的点
利用 t 更新 S 外点到 S 的距离
st[t] = true
n 次迭代之后所有点都已加入到 S 中
联系:Dijkstra 算法是更新到起始点的距离,Prim 是更新到集合 S 的距离
代码
1 |
|
Kruskal
859. Kruskal算法求最小生成树
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。
求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。
输出格式
共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
。
数据范围
图中涉及边的边权的绝对值均不超过 10000。
输入样例:
1 | 4 5 |
输出样例:
1 | 6 |
AC
思路
- 将所有边按照权重从小到大排序 O(mlog(m))
- 枚举每条边(a, b, 权重c) O(m)
1 | if a, b 两点不连通 |
第二步与 连通块中点的数量 相似
使用并查集,查询两个结点是否属于一个集合, 合并两个结点
代码
1 |
|
染色法判定二分图
860. 染色法判定二分图
给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。
请你判断这个图是否是二分图。
输入格式
第一行包含两个整数 n 和 m。
接下来 m 行,每行包含两个整数 u 和 v,表示点 u 和点 v 之间存在一条边。
输出格式
如果给定图是二分图,则输出 Yes
,否则输出 No
。
数据范围
输入样例:
1 | 4 4 |
输出样例:
1 | Yes |
AC
思路
染色法
- 将所有点分成两个集合,使得所有边只出现在集合之间,就是二分图
- 二分图:一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
dfs版本
- 代码思路:
- 染色可以使用1和2区分不同颜色,用0表示未染色
- 遍历所有点,每次将未染色的点进行dfs, 默认染成1或者2
- 由于某个点染色成功不代表整个图就是二分图,
因此只有某个点染色失败才能立刻break/return
- 染色失败相当于存在相邻的2个点染了相同的颜色
代码
1 |
|
匈牙利算法
861. 二分图的最大匹配
给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2个点(编号 1∼n2),二分图共包含 m 条边。
数据保证任意一条边的两个端点都不可能在同一部分中。
请你求出二分图的最大匹配数。
二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。
输入格式
第一行包含三个整数 n1、 n2 和 m。
接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。
输出格式
输出一个整数,表示二分图的最大匹配数。
数据范围
输入样例:
1 | 2 2 4 |
输出样例:
1 | 2 |
AC
思路
什么是最大匹配?
匹配:在图论中,一个「匹配」是一个边的集合,其中任意两条边都没有公共顶点。
最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。
下面是一些补充概念:
- 完美匹配:如果一个图的某个匹配中,所有的顶点都是匹配点,那么它就是一个完美匹配。
- 交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路。
- 增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替 路称为增广路(agumenting path)。
算法模板
存图模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//邻接表写法,存稀疏图
int h[N],ne[N],e[N],idx;
//n1,n2分别是两个点集的点的个数
int n1,n2,m;
void add(int a , int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void init() {
memset(h,-1,sizeof h);
}
//存边只存一边就行了,虽然是无向图。
for(int i = 0 ; i < n1 ; i ++) {
int a,b;
cin>>a>>b;
add(a,b);
}匈牙利模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//match[j]=a,表示女孩j的现有配对男友是a
int match[N];
//st[]数组我称为临时预定数组,st[j]=a表示一轮模拟匹配中,女孩j被男孩a预定了。
int st[N];
//这个函数的作用是用来判断,如果加入x来参与模拟配对,会不会使匹配数增多
int find(int x) {
//遍历自己喜欢的女孩
for(int i = h[x] ; i != -1 ;i = ne[i]) {
int j = e[i];
if(!st[j]) { //如果在这一轮模拟匹配中,这个女孩尚未被预定
st[j] = true;//那x就预定这个女孩了
//如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功,更新match
if(!match[j]||find(match[j])) {
match[j] = x;
return true;
}
}
}
//自己中意的全部都被预定了。配对失败。
return false;
}
//记录最大匹配
int res = 0;
for(int i = 1; i <= n1 ;i ++) {
//因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
memset(st,false,sizeof st);
if(find(i))
res++;
}递归过程
代码
1 |
|