目录
前言:
DFS
算法思想:
通过递归函数的方式,实现深度优先搜索,即对每一条路都会走到头才返回上一层,基本包括以下五个部分:
结束条件: 递归终止的条件,避免爆栈
递归顺序: 选择合适的递归顺序
进行操作: 对当前层进行操作
递归下一层: 递归下一层
恢复现场: 恢复当前层的进行的操作
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10;
int n;
int path[N];
bool st[N];
void dfs(int u)
{
// 终止条件
if (u == n)
{
for (int i = 0; i < n; i ++) cout << path[i] << ' ';
cout << endl;
return ;
}
// 递归顺序
for (int i = 1; i <= n; i ++)
if (st[i] != true)
{
// 进行操作
path[u] = i;
st[i] = true;
// 递归下一层
dfs(u + 1);
// 恢复现场
path[u] = 0;
st[i] = false;
}
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
cin >> n;
dfs(0);
return 0;
}
BFS
算法思想:
利用队列,每次选取距离当前点最近的点进行遍历,如图所示,红色为点的遍历层次,具有最短路的性质
注意点:
- bfs求最短路仅限制权重为1的图
模板:
void bfs()
{
queue<> q; // 宽搜队列
// 起点入队
while ( 队列非空 )
{
// 取队头
// 弹出队头
// 拓展队头
// 继续入队
}
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 105;
typedef pair<int, int> PII;
int n, m;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; // 偏移量
int g[N][N]; // 存图
int d[N][N]; // 存每个点到0,0的距离
queue<PII> q;
bool check(int x, int y)
{
return x < n && x >= 0 && y < m && y >= 0;
}
int bfs()
{
memset(d, -1, sizeof d); // 将所有点到起点的距离置为 -1
q.push({0, 0}); // 将起点入队
d[0][0] = 0;
while(!q.empty()) // 当队列非空
{
auto t = q.front(); // 取队头元素
q.pop(); // 弹出队头
for (int i = 0; i < 4; i ++)
{
// 走一步可到达的点
int x = t.first + dx[i];
int y = t.second + dy[i];
if (check(x, y) && g[x][y] != 1 && d[x][y] == -1) // x,y没有出界,走一步的点不是墙,走一步的点并且没被搜过
{
d[x][y] = d[t.first][t.second] + 1; // 记录当前这点到原点的距离
q.push({x, y}); // 将该点入队
}
}
}
return d[n - 1][m - 1];
}
int main( )
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 0; i < n; i ++)
for (int j = 0; j < m; j ++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
树与图的深度优先遍历
算法思想:
利用邻接表(拉链法) 存图,进行深度优先遍历,对遍历过的点进行标记
模板:
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, M = N *2;
int h[N], e[M], ne[M], idx;
bool st[N];
int n;
int ans = N;
void add(int a, int b) // 加边
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int dfs(int u) // 返回以u为根的树的连通点的数量
{
st[u] = true; // 当前点被搜过
int sum = 1, res = 0;
// sum为以u为根的树的连通点的数量
// res为将该点删除后其余连通块点个数的最大值, 由两部分取max构成
// 第一部分,其所有子树的sum, 第二部分,其头上的树的点个数
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (st[j] != true)
{
int s = dfs(j); // 求子树的联通点数量
sum += s; //
res = max(res, s); // 第一部分
}
}
res = max(res, n - sum); // 第二部分
ans = min(ans, res); // 全局sum取所有点被删除后所有res情况中的最小值
return sum;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++)
{
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
cout << ans << endl;
return 0;
}
树与图的广度优先遍历
算法思想:
利用邻接表(拉链法) 存图,进行广度优先遍历,对遍历过的点进行标记,每次对队头进行拓展后入队新点
模板:
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
例题:
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 5, M = N * 2;
int n, m;
int h[N], e[M], ne[M], idx;
int d[N]; // 距离
queue<int> q; // 宽搜队列
void add(int a, int b) // 加边
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int bfs()
{
// 起点入队
d[1] = 0;
q.push(1);
while (!q.empty()) // 队列非空
{
auto t = q.front(); // 取队头
q.pop(); // 弹出队头
// 拓展队头
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i]; // 取值
if (d[j] == -1) // 没有搜过
{
d[j] = d[t] + 1; // 更新距离
q.push(j); // 拓展点入队
}
}
}
return d[n];
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(d, -1, sizeof d);
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i ++)
{
int a, b;
cin >> a >> b;
add(a, b);
}
cout << bfs() << endl;
return 0;
}
拓扑排序
算法思想:
任何有向无环图都存在拓扑序,对于拓扑排序,每次选取入度为0的点删掉,并把以该点为起点的边删掉,将新的入度为0的点入队,重复直至排序完成,如图红色表示每个点的入度:
模板:
bool topsort( )
{
int index = 0;
for (int i = 1; i <= n; i ++)
if (d[i] == 0)
q.push(i); // 入度为0的点入队
while (!q.empty()) // 队列非空
{
auto t = q.front(); // 取队头
q.pop();
ans[index ++] = t; // 存进ans
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j] --; // 删 t -> j 的边,j 的入度 - 1
if (d[j] == 0)
q.push(j); // 若入度为0,则进队
}
}
return index == n;
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
int h[N], e[N], ne[N], idx;
int d[N]; // 每个点的入度
int n, m;
int ans[N]; // 存放排序
queue<int> q; // 宽搜队列
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool topsort( )
{
int index = 0;
for (int i = 1; i <= n; i ++)
if (d[i] == 0)
q.push(i); // 入度为0的点入队
while (!q.empty()) // 队列非空
{
auto t = q.front(); // 取队头
q.pop();
ans[index ++] = t; // 存进ans
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
d[j] --; // 删 t -> j 的边,j 的入度 - 1
if (d[j] == 0)
q.push(j); // 若入度为0,则进队
}
}
return index == n;
}
int main( )
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i ++)
{
int a, b;
cin >> a >> b;
add(a, b); // 加边
d[b] ++; // 入度 + 1
}
if (topsort())
{
for (int i = 0; i < n; i ++)
cout << ans[i] << ' ';
}
else
puts("-1");
return 0;
}
最短路问题
解题思路:
Dijkstra
算法思想:
朴素Dijkstra算法: dist[] 表示每个点到起点的距离
步骤一: 将第一个点加到最短路集合里,dist[1] = 0,其他点dist[i]=+∞,表示第一个点到起点的距离为0
步骤二: n次循环,找出不在集合中距离最近的点t
步骤三: 将 t 放到集合里
步骤四: 用 t 更新其他点的距离,取小值
堆优化版Dijkstra算法 :步骤二的时间复杂度经过n次循环时间复杂度为O(n²) 可以用堆来进行优化,将整个时间复杂度降为O(mlogn)
模板:
// 朴素 Dijkstra
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
// 源点入集合
dist[1] = 0;
// n次循环
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;
// 加入集合
st[t] = true;
// 更新距离
for (int j = 1; j <= n; j ++)
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if (dist[n] == 0x3f3f3f3f) return -1;
else
return dist[n];
}
// 堆优化版
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII>> heap;
// 源点入队
heap.push({0, 1});
dist[1] = 0;
while (!heap.empty())
{
// 求距离最小的点
auto t = heap.top();
heap.pop();
int distance = t.first, index = t.second; // 取距离和点号
if (st[index]) continue;
st[index] = true;
// 更新以index为起点的边连接的点的距离
for (int i = h[index]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[index] + w[i])
{
dist[j] = dist[index] + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
例题:
AcWing 849. Dijkstra求最短路 I
AcWing 850. Dijkstra求最短路 II
AC代码:
// 朴素 Dijkstra
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510;
int n, m;
int g[N][N], dist[N];
bool st[N];
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
// 源点入集合
dist[1] = 0;
// n次循环
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;
// 加入集合
st[t] = true;
// 更新距离
for (int j = 1; j <= n; j ++)
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
if (dist[n] == 0x3f3f3f3f) return -1;
else
return dist[n];
}
int main( )
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
cin >> n >> m;
memset(g, 0x3f, sizeof g);
while (m --) // 加边
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
int t = dijkstra();
cout << t << endl;
return 0;
}
// 堆优化Dijkstra
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2e5;
typedef pair<int, int> PII;
int n, m;
int h[N];
int w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
w[idx] = c, e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);
priority_queue<PII, vector<PII>, greater<PII>> heap;
// 源点入队
heap.push({0, 1});
dist[1] = 0;
while (!heap.empty())
{
// 求距离最小的点
auto t = heap.top();
heap.pop();
int distance = t.first, index = t.second; // 取距离和点号
if (st[index]) continue;
st[index] = true;
// 更新以index为起点的边连接的点的距离
for (int i = h[index]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[index] + w[i])
{
dist[j] = dist[index] + w[i];
heap.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
else return dist[n];
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m --)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int t = dijkstra();
cout << t << endl;
return 0;
}
Bellman-ford
算法思想:
循环n次 对于所有的边 a -> b 权为 w,进行松弛操作: 在原dist[b] 和dist[a] + w 中取小值赋给 dist[b]
注意点:
- 边用结构体存储即可
- 对于边数的限制 k 只需调整循环为k次即可,并且需要采用备份,保证每次只动一条边
模板:
int bellmanford()
{
memset(dist, 0x3f3f3f3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++) // k 次循环
{
memcpy(backup, dist, sizeof dist); // 备份
for (int j = 0; j < m; j ++) // m 条边
{
int a = edge[j].a, b = edge[j].b, w = edge[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
return dist[n];
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 505, M = 10010;
int n, m, k;
int dist[N], backup[N];
struct edge{
int a, b, w;
}edge[M];
int bellmanford()
{
memset(dist, 0x3f3f3f3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < k; i ++) // k 次循环
{
memcpy(backup, dist, sizeof dist); // 备份
for (int j = 0; j < m; j ++) // m 条边
{
int a = edge[j].a, b = edge[j].b, w = edge[j].w;
dist[b] = min(dist[b], backup[a] + w);
}
}
return dist[n];
}
int main( )
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
cin >> n >> m >> k;
for (int i = 0; i < m; i ++) cin >> edge[i].a >> edge[i].b >> edge[i].w;
int t = bellmanford();
if (t > 0x3f3f3f3f / 2) cout << "impossible" << endl;
else
cout << t << endl;
return 0;
}
SPFA
算法思想:
对于Bellman - ford 算法的松弛操作,我们可以发现,只有当 dist[a] 变小时,min操作才有可能变小,所以利用bfs来做优化,
步骤一: 先把起点放到队列中,只要队列不空,
步骤二: 每次取队头,进行更新操作(因为t变小了,所以t的出边到的点才有可能变小)
步骤三: 将dist[ ]变小的结点放入队列,若队列中有则不需要再加
SPFA 判断负环: 用cnt数组记录从1号点到每个点的边数,由于一共n个点,一条路最多n - 1条边,所以若cnt >= n则说明有负环
模板:
// 求最短路
int spfa()
{
memset(dist, 0x3f, sizeof dist);
// 初始
queue<int> q;
q.push(1);
dist[1] = 0;
st[1] = true;
while (!q.empty())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (st[j] == false)
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
// 判断负环
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
int n, m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N], cnt[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
queue<int> q;
// 初始
dist[1] = 0;
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);
}
while (!q.empty())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;
if (st[j] == false)
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i ++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
bool t = spfa();
if (t) cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 1e5 + 5;
int n, m;
int h[N], e[N], w[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa()
{
memset(dist, 0x3f, sizeof dist);
// 初始
queue<int> q;
q.push(1);
dist[1] = 0;
st[1] = true;
while (!q.empty())
{
auto t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
if (st[j] == false)
{
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 0; i < m; i ++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
int t = spfa();
if (t == 0x3f3f3f3f) cout << "impossible" << endl;
else
cout << t << endl;
return 0;
}
Floyd
算法思想:
通过三重循环,每次判断是直接从i -> j的路径短,还是i ->k -> j 的路径短,选小值更新
模板:
void floyd()
{
for (int k = 1; k <= n; k ++)
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 205, INF = 1e9;
int g[N][N];
int n, m, k;
void floyd()
{
for (int k = 1; k <= n; k ++)
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
}
int main( )
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
if (i == j) g[i][j] = 0;
else
g[i][j] = INF;
for (int i = 0; i < m; i ++)
{
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
floyd();
while (k --)
{
int a, b;
cin >> a >> b;
if (g[a][b] > INF / 2) cout << "impossible" << endl;
else
cout << g[a][b] << endl;
}
return 0;
}
最小生成树
解题思路:
对于稠密图采用朴素Prim算法,稀疏图采用Kruskal算法
Prim
算法思想:
与最短路算法不同,最小生成树的dist[] 表示该点到已经生成树的连通块的距离(不是起点),n 次循环,每次将距离连通块最小的点加入集合,并进行距离更新,若该点不是第一个将被加入集合点并且距离还为正无穷,说明有点不连通,则不存在最小生成树。
注意点:
模板:
int prim()
{
// 初始化距离
memset(dist, 0x3f, sizeof dist);
int res = 0;
// n 次循环,加入 n 个点
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 != 0 && dist[t] == INF) return INF;
// 计算最小生成树的总长度
st[t] = true; // 加入集合
// 更新集合
for (int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
if (i != 0) res += dist[t];
}
return res;
}
例题:
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 505, INF = 0x3f3f3f3f;
int g[N][N];
int n, m;
int dist[N]; // 表示每个点到已经连通的块的距离
bool st[N]; // 是否在最小生成树集合中
int prim()
{
// 初始化距离
memset(dist, 0x3f, sizeof dist);
int res = 0;
// n 次循环,加入 n 个点
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 != 0 && dist[t] == INF) return INF;
// 计算最小生成树的总长度
st[t] = true; // 加入集合
// 更新集合
for (int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
if (i != 0) res += dist[t];
}
return res;
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i ++) g[i][i] = 0;
cin >> n >> m;
while(m --) // 输入 m 条边
{
int a, b, c;
cin >> a >> b >> c;
if (a == b) continue;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int t =prim();
if (t == INF) cout << "impossible" << endl;
else
cout << t << endl;
return 0;
}
Kruskal
算法思想:
先对所有边进行排序,利用并查集,枚举每一条边,若两条边的两点不连通(不在一个集合)则进行集合合并(并查集基本操作),每次记录生成树的长度
注意点:
模板:
int kruskal()
{
// 初始化并查集
for (int i = 1; i <= n; i ++) p[i] = i;
sort(edge, edge + m);
// res 为最小生成树长度,cnt 为生成树中点的数量
int res = 0, cnt = 0;
// 枚举 m 条边
for (int i = 0; i < m; i ++)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
a = find(a), b = find(b);
if (a != b) // 不在一个集合中
{
// 连通
p[a] = b;
// 总和增加
res += w;
cnt ++;
}
}
if (cnt < n - 1) return INF;
else
return res;
}
例题:
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200005, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct edge
{
int a, b, w;
// 重载小于号
bool operator< (const edge &W) const
{
return w < W.w;
}
}edge[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int kruskal()
{
// 初始化并查集
for (int i = 1; i <= n; i ++) p[i] = i;
sort(edge, edge + m);
// res 为最小生成树长度,cnt 为生成树中点的数量
int res = 0, cnt = 0;
// 枚举 m 条边
for (int i = 0; i < m; i ++)
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
a = find(a), b = find(b);
if (a != b) // 不在一个集合中
{
// 连通
p[a] = b;
// 总和增加
res += w;
cnt ++;
}
}
if (cnt < n - 1) return INF;
else
return res;
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 0; i < m; i ++) // 加边
{
int a, b, w;
cin >> a >> b >>w;
edge[i] = {a, b, w};
}
int t = kruskal();
if (t == INF) cout << "impossible" << endl;
else
cout << t << endl;
return 0;
}
二分图
解题思路:
染色法判定二分图
算法思想:
有奇数环的图一定不是二分图,所以我们用dfs对于每个结点进行两种颜色的交替染色,保证当前点的所有出边的点染上与当前点不同的颜色,若发现邻点已经被染过色并且与自己颜色相同,则说明不是二分图
模板:
bool dfs(int u, int c)
{
// 当前点进行染色
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!color[j]) // 邻点未染色
{
if (!dfs(j, 3 - c)) return false;
}
else if (color[j] == color[u]) return false;
// 邻点染色但与当前点相同
}
return true;
}
例题:
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 5, M = N * 2;
int n, m;
int h[N], e[M], ne[M], idx;
int color[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
bool dfs(int u, int c)
{
// 当前点进行染色
color[u] = c;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!color[j]) // 邻点未染色
{
if (!dfs(j, 3 - c)) return false;
}
else if (color[j] == color[u]) return false;
// 邻点染色但与当前点相同
}
return true;
}
int main()
{
cin.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(h, -1, sizeof h);
cin >> n >> m;
while (m --)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
bool flag = true;
for (int i = 1; i <= n; i ++)
if (!color[i])
{
if(!dfs(i, 1))
{
flag = false;
break;
}
}
if (flag) cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}
匈牙利算法
算法思想:
判断二分图两部分的最大匹配,即在不同部分里的点之间只有一条边相连,使得边最多即可。
遍历左部分所有点,如图,根据 x 的出边,为他分配点 y,但若当前出边的点 y 已经被分配给了某个点 m ,则判断该点 m 是否有第二选择,若有就腾出位置,将其原有的分配让给 x,而m与p配对
模板:
例题:
AC代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int n1, n2, m;
int h[N], e[M], ne[M], 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 != -1; 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.tie(0), cout.tie(0);
ios::sync_with_stdio(false);
memset(h, -1, sizeof h);
cin >> n1 >> n2 >> m;
while (m --)
{
int a, b;
cin >> a >> b;
add(a, b);
}
int res = 0;
for (int i = 1; i <= n1; i ++)
{
memset(st, false, sizeof st);
if (find(i)) res ++;
}
cout << res << endl;
return 0;
}