Screenshot_2021-11-12-16-34-35-0901979176_EDIT_1636706124200

最小生成树:稀疏图 一般使用朴素版普里姆算法,稠密图一般使用克鲁斯卡尔算法,而堆优化版的普里姆算法一般弃用

最小生成树


朴素普里曼算法

实现:

  1. 找到集合外到结果集合的距离最近的点
  2. 标记点添加入结果集合
  3. 根据新点更新其他点到结果集合的距离

核心算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int prim(){
memset(dist, 0x3f, sizeof dist);
int ans = 0;

for(int i = 0; i < n; i++){
int t = -1;
for(int j = 1; j <= n; j++){
if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
}

if(i && dist[t] == INF) return INF;

if(i) ans += dist[t];
// for(int j = h[t]; ~j; j = ne[j]) dist[e[j]] = min(dist[e[j]], w[j]); //邻接表
for(int i = 1; i <= n; i++) dist[i] = min(dist[i], g[t][i]);

st[t] = true;
}

return ans;
}

克鲁斯卡尔算法

实现:

  1. 将所有边按权重从小到大排序 O(mlogm)O_{(m\log_m)}
  2. 枚举每条边a-b, 权重 c,如果当前结果集合中a-b是不存在的那么就将这条边添加到结果集合中去。使用并查集 O(m)O_{(m)}

代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

const int N = 100010;

int n, m;
int p[N];

pair<int, pair<int, int>> edges[N<<1];

int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}

int main(){
cin>>n>>m;
for(int i = 0 ; i < m; i++){
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
edges[i] = {w, {a, b}};
}

sort(edges, edges+m);

for(int i = 0; i <= n; i++) p[i] = i;

int ans = 0, cnt = 0;

for(int i = 0; i < m; i++){
int a = edges[i].second.first, b = edges[i].second.second;

a = find(a), b = find(b);
if(a != b){
p[a] = b;
ans += edges[i].first;
cnt++;
}
}

if(cnt < n-1) puts("impossible");
else cout<<ans;
}

二分图


二分图的定义:一个图是二分图当且仅当图中不含奇数个节点构成的环

染色法判断二分图

实现:

  1. 使用深度优先搜索遍历整个图
  2. 对遍历到的节点进行间隔染色一步红一步黑
  3. 发现已经染色并且颜色和需要期待染的相同则意味着目前出现了奇数环

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
bool dfs(int u, int c){
color[u] = c;
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(!color[j]){
if(!dfs(j, -c)) return false;
}
else if(color[j] == c) return false;
}

return true;
}

匈牙利算法

实现(男生组匹配女生组):

  1. 男生点遍历所有有联系的女生点判断女生点是否无其他联系
  2. 如果无,这该男生点与女生点建立联系
  3. 如果有,则判断与该女生点有联系的男生点是否能重新建立联系(递归)
  4. 理论复杂度 O(mn)O_{(m*n)} ,但实际会小很多
image-20211120210826354

代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int N = 510, M = 100010;

int n1, n2, m;
int e[M], ne[M], h[N], idx;
int match[N];
bool st[N];

void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

bool find(int x){
for(int i = h[x]; ~i; i = ne[i]){
int j = e[i];
if(!st[j]){
st[j] = true;
if(match[j] == 0 || find(match[j])){
match[j] = x;
return true;
}
}
}
return false;
}

int main(){
cin>>n1>>n2>>m;

memset(h, -1, sizeof h);

while(m--){
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}

int ans = 0;
for(int i = 1; i <= n1; i++){
memset(st, false, sizeof st);
if(find(i)) ans++;
}

cout<<ans;
return 0;
}