图论
树: 无环连通图
所以呢, 理解图即可理解树
1.图的存储
对于图的存储有两种
-
运用邻接矩阵 主要用于稠密图
稠密图 m 约等于 n^2, m 表示边数, n 表示结点个数
// 假设边数最大为N 条
typename q[N][N]
// q[i][j] 表示从i 到 j 有一条边 权重为q[i][j], (权重:比如距离, 有还是没有)
// if q[i][j] 表示距离需要初始化
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j ++) {
if (i == j) q[i][j] = 0;
else q[i][j] = 0x3f3f3f3f;// 这里定义0x3f3f3f3f为最大, 其实不是2^31 - 1 > 0x3f3f3f3f, 但是好memset(q, 0x3f, sizeof q);
}
}
//or
memset(q, 0x3f, sizeof q);
for (int i = 0; i < n; i++) {
q[i][i] = 0;
}
-
运用邻接表,说白了就是N个单链表, 也可以理解在哈希冲突中的链地址法
主要运用于稀疏图
稀疏图:m 约等于 n, m, n 如上
// 假设最多N个节点, M条边
int h[N], e[M], ne[M], idx;
// 与单链表非常像, 就是head变成了head[N]
// 插入操作
void add(int a, int b) {
e[idx] = b;
ne[idx] = a;
h[a] = idx++;
}
// 这样就形成了从a到b的边
// 注意一定要init
void init() {
memset(h, -1, sizeof h);0
}
// 建议用一个函数包起来, 而不是直接在main() 中写, 因为会经常忘记
!](…/…/IMG_0607.PNG)
假设你需要创建一个这样的图
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], ne[N], idx;
//init
void init() {
memset(h, -1, sizeof h);
}
// insert
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
int main() {
// 记得初始化
init();
add(1, 2);
add(2, 4);
add(1, 3);
add(3, 4);
add(1, 4);
return 0;
}
这时候你可能会问, 如果a到b之间有距离, 那么中间的距离怎么办呢?
- 可以将e[N]变成结构体
struct E{
// 表示e[N]中的e, w代表a到b的距离
int e, w;
}e[N];
- 可以再加入一个数组
// w数组保存的是距离
int h[N], e[N], ne[N], w[N], idx;
void add(int a, int b, int len) {
e[idx] = b;
w[idx] = len;
ne[idx] = h[a];
h[a] = idx++;
}
2.图的遍历
// 就是dfs的简单模式
// 参考代码
// book数组标记哪些结点被标记过
bool book[N];
// int u 代表现在遍历到了结点u
void dfs(int u) {
// 这个点已经遍历过了
book[u] = true;
printf("%d ", u);
// 探索u理解的点
for (int i = h[u]; i != -1; i = ne[i]) {
// i 表示的是单链表的next
// j 表示的是当前结点, 就是单链表里面的data
int j = e[i];
// 如果j 没有被遍历就遍历j
if (!book[j]) {
dfs(j);
}
}
}
ex:
[题目链接](846. 树的重心 - AcWing题库)
无向边解释:
无向边就是没有方向的边, 就像路一样(假设a,b 之间有一条无向边, 那么a可以到b, b 也可以到a), 现实中类似:人行道
有向边解释:
假设a到b有一条有向边, 那么a可以到b, 但是b不能到a;(类似显示中的:单向路)
树重心解释
那么这个问题该怎么解决呢?
- 首先, 我们可以发现对于这点的上面那个连通块是无法求结点个数(因为dfs是从上面下来的book[上面那个结点] = true), 但是你会发现上面那个连通快 == n - sum(子树节点和) - 1, n -> 代表结点总个数, 1 表示结点本身, 那么这个问题就转化为求sum(子树结点和).
- 那么怎么求子树节点和, 其实我们可以在分, 只需要遍历一下这个结点, 如何求每个子树的结点就可以了.
- 最终问题转化为: 如何求子树结点个数
这里可以用dfs, 也可以用bfs, 但是这里讲的是dfs那就用dfs吧
测试数据:
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
样例输出:
4
参考代码:
#include<bits/stdc++.h>
using namespace std;
// N 表示结点个数, M 表示边的个数, 因为是无向边, 所以是2*(N - 1);
const int N = 1e5 + 10, M = 2 * N;
int n;
// 邻接表
int h[N], e[M], ne[M], idx;
// 定义答案为无穷大, 因为要取个个结点的最小值
int ans = 0x3f3f3f3f;
// 用来表示结点是否访问过
bool book[N];
// 初始化
void init() {
memset(h, -1, sizeof h);
}
// insert
// 一般使用的是头插
// 你用其他也可以(比如中间插, 尾插)
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
}
// dfs返回的是以step为root所含结点个数
int dfs(int step) {
// step 结点已经访问过了
book[step] = true;
// sum 来记录以step为root的结点个数
// res 表示子树中结点数量的最大值
int sum = 1, res = 0;
// 遍历step
for (int i = h[step]; i != -1; i = ne[i]) {
// j 为结点编号
int j = e[i];
// 如果这个结点没有访问过
if (!book[j]) {
// t 为以j 为root的结点个数
int t = dfs(j);
// 结点的最大值
res = max(res, t);
// 求和
sum += t;
}
}
// 将子树最大与树外面的求最大
res = max(res, n - sum);
// 这就是删除step结点后连通块结点的最大值 res
// 再将最大值与答案求最小即为重心
ans = min(res, ans);
return sum;
}
int main() {
init();
cin >> n;
for (int i = 1; i < n; i++ ) {
int a, b;
scanf("%d%d", &a, &b);
// 无向边所以做一条a->b, 在做一条b->a;
add(a, b);
add(b, a);
}
dfs(1);
cout << ans << endl;
return 0;
}
bfs() 遍历图
[链接](847. 图中点的层次 - AcWing题库)
输入样例:
4 5
1 2
2 3
3 4
1 3
1 4
输出样例:
1
重边解释:
就算说之前出现过a到b这条边, 后面又有a到b这条边
自环解释:
就是有a到a这条边
学完后面后可以尝试用单源最短路来实现
3.拓扑排序
-
什么叫入度与出度以及什么叫拓扑序列?
解法:
-
对于拓扑序列以及入度的描述, 当我们需要输出某个值时, 一定会使他上面的输出,就相当于没有输出的里面这个点的入度一定为0
-
所以我们可以用一个数组d来维护入度, 在add() 时可以让d[b] ++, 当a输出时d[b] –
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
// n, m相等, 用邻接矩阵
int h[N], e[N], ne[N], idx;
// 维护入度
int d[N];
int n, m;
// 用手搓队列
// 下面有解释
int que[N], tt, hh;
void add(int a, int b) {
e[idx] = b;
ne[idx] = h[a];
d[b] ++;
h[a] = idx++;
}
// 判断是否合法
bool topsort() {
// 1. 将入度为0的点加入队列
for (int i = 1; i <= n; i++) {
if (!d[i]) {
que[tt++] = i;
}
}
// 遍历队列, 因为队列里面存放的是入度为0的结点, 需要将他相邻的点的入度减一
while(hh < tt) {
// 取出队头元素
int t = que[hh++];
// 遍历 让其相邻的点的入度减1
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
d[j] -- ;
// 如果说d[j] == 0, 就让他入队
if (!d[j]) {
que[tt ++] = j;
}
}
}
// tt 对于n 说明队里面有n个元素合法
// 并且你会发现que里面存的就是拓扑序列, 这就是为什么要用手搓队, 而不用queue<int> que;
return tt == n;
}
void init() {
memset(h, -1, sizeof h);
}
int main() {
init();
cin >> n >> m;
while(m -- ) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
}
if (topsort()) {
for (int i = 0; i < n; i++) {
printf("%d ", que[i]);
}
}else {
printf("-1\n");
}
system("pause");
return 0;
}
4. dijkstra(狄克斯特拉)算法
对于单源路最推荐spfa他是正数与负数均可以做
对应代码
// O(n ^ 2)
// 朴素版的用于稠密图,我们开邻接矩阵
// n 结点数
int dist[N], n;
bool st[N];
int dijkstra() {
// 初始化为inf
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 循环遍历n - 1 遍
// 因为一次只能加一个点进来, 本来要n 次, 但是最后一次就他自己一个人, 所以无需遍历
for (int i = 0; i < n - 1; i ++ ) {
int t = -1;
for (int j = 1; j <= n; j ++) {
// !st[j] 表示j 不在st里面
// t == -1 or dist[t] > dist[j]
// t == -1其实就是说, 想让t 初始化为不在st中的第一个, 但是你不知道是哪个
// dist[t] > dist[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] + t到j的距离);
}
}
return dist[n];
}
重边:
我们取重边小的作为q[a][b], 对于邻接矩阵,不管
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int q[N][N];
int dist[N];
bool st[N];
int n, m;
int dijkstra() {
// 初始化为inf
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 循环遍历n - 1 遍
// 因为一次只能加一个点进来, 本来要n 次, 但是最后一次就他自己一个人, 所以无需遍历
for (int i = 0; i < n - 1; i ++ ) {
int t = -1;
for (int j = 1; j <= n; j ++) {
// !st[j] 表示j 不在st里面
// t == -1 or dist[t] > dist[j]
// t == -1其实就是说, 想让t 初始化为不在st中的第一个, 但是你不知道是哪个
// dist[t] > dist[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] + q[t][j]);
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main() {
// 初始化为inf, 因为有重边
memset(q, 0x3f, sizeof q);
cin >> n >> m;
while ( m -- ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 对应上面为什么要初始化
q[a][b] = min(q[a][b], c);
}
cout << dijkstra() << endl;
return 0;
}
5.dijkstra()算法优化
如何优化呢?
- 对于上述第二步找最小值, 我们可以用小根堆来实现
// 优化版的对应于稀疏图
// O(mlogn) 如果是稠密图, m == n ^ 2 --> n^2 logn > n^2
// 稀疏图用邻接表来存储
typedef pair<int, int> pii;
int dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// pair.frist 存距离, pair.second 存编号
// 因为greater<> 是按照字典序排列, 所以不能反, 不知道的可以百度
priority_queue<pii, vector<pii>, greater<pii>> que;
// 将第一个点加进去
que.push({0, 1});
while(que.size()) {
// 拿出最小值
auto t = que.top();
que.pop();
// 先将其表示出来, 不表示也没有关系, 只是写起来有点麻烦
int distance = t.first, location = t.second;
// 因为用priority_queue会出现冗余,
// 冗余下面会解释
// 就要判断一下是否是冗余
// 之前就在st里面了, 就不用更新了, 因为没有必要, 节约时间
if (st[location]) continue;
st[location] = true;
// 更新临近的点
for (int i = h[location]; i != -1; i = ne[i]) {
int j = e[i];
// w[] 存储的是距离, 上面邻接表已经讲过了
if (dist[j] > distance + w[i]) {
dist[j] = distance + w[i];
// dist改变后我们就然他加入que
// 这就是冗余的来源, 前面有个数据更新了dist[a], a 进来que
// 后来进来了本身是要将原理的a中的que更改, 但是priority_queue没有这个功能
// 没有办法就只好把他在加进去, 这样就形成了冗余
// 使得时间复杂度变成了mlogm但是m 最多也就n^2, 所以mlogm <= 2mlogn = mlogn
que.push({dist[j], j});
}
}
}
return dist[n];
}
[题目链接](850. Dijkstra求最短路 II - AcWing题库)
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int > pii;
const int N = 2e5 + 10;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
int n, m;
// 优化版的对应于稀疏图
// O(mlogn) 如果是稠密图, m == n ^ 2 --> n^2 logn > n^2
// 稀疏图用邻接表来存储
int dijkstra() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// pair.frist 存距离, pair.second 存编号
// 因为greater<> 是按照字典序排列, 所以不能反, 不知道的可以百度
priority_queue<pii, vector<pii>, greater<pii>> que;
// 将第一个点加进去
que.push({0, 1});
while(que.size()) {
// 拿出最小值
auto t = que.top();
que.pop();
// 先将其表示出来, 不表示也没有关系, 只是写起来有点麻烦
int distance = t.first, location = t.second;
// 因为用priority_queue会出现冗余,
// 冗余下面会解释
// 就要判断一下是否是冗余
// 之前就在st里面了, 就不用更新了, 因为没有必要, 节约时间
if (st[location]) continue;
st[location] = true;
// 更新临近的点
for (int i = h[location]; i != -1; i = ne[i]) {
int j = e[i];
// w[] 存储的是距离, 上面邻接表已经讲过了
if (dist[j] > distance + w[i]) {
dist[j] = distance + w[i];
// dist改变后我们就然他加入que
// 这就是冗余的来源, 前面有个数据更新了dist[a], a 进来que
// 后来进来了本身是要将原理的a中的que更改, 但是priority_queue没有这个功能
// 没有办法就只好把他在加进去, 这样就形成了冗余
// 使得时间复杂度变成了mlogm但是m 最多也就n^2, 所以mlogm <= 2mlogn = mlogn
que.push({dist[j], j});
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
while(m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
return 0;
}
手搓堆版请自行实现(比较麻烦)
[用这个堆](839. 模拟堆 - AcWing题库)
6.bellman_ford算法
// 因为bellman_ford算法需要遍历所有的边, 所有我们可以建立一个结构体来存储, 可以不用其他的(邻接表,邻接矩阵)
struct edge{
int a, b, w;
}Edge[M];
int bellman_ford() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 这层遍历的意思是:不超过n条边到达n结点
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j ++) {
// 你觉得Edge很难写的话可以这样
// int a = Edge[j].a, b = Edge[j].b, w = Edge[j].w;
// dist[b] = min(dist[b], dist[a] + w);
dist[Edge[j].b] = min(dist[Edge[j].a] + Edge[j].w, dist[Edge[j].b]);
}
}
}
其实这段代码是有问题的
修改后:
那该如何解决呢?
- 其实你会发现, dist如果是上一次的结果就好了, 这样就不会影响结果
// 因为bellman_ford算法需要遍历所有的边, 所有我们可以建立一个结构体来存储, 可以不用其他的(邻接表,邻接矩阵)
struct edge{
int a, b, w;
}Edge[M];
// 用来保存dist的上一次结果
int back[N];
int bellman_ford() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 这层遍历的意思是:不超过n条边到达n结点
for (int i = 0; i < n; i++) {
// 将dist的上一次结果复制给back
memcpy(back, dist, sizeof dist);
for (int j = 0; j < m; j ++) {
// 这里将back数组代替为原来的dist
dist[Edge[j].b] = min(back[Edge[j].a] + Edge[j].w, dist[Edge[j].b]);
}
}
return dist[n];
}
ex:
[题目链接](853. 有边数限制的最短路 - AcWing题库)
数据范围
1≤n,k≤500
1≤m≤10000
1≤x,y≤n
任意边长的绝对值不超过 10000
输入样例:
3 3 1
1 2 1
2 3 1
1 3 3
输出样例:
3
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, M = 510;
struct ss {
int a, b, w;
}s[N];
int n, m, k;
int dist[M], back[M];
// 因为bellman_ford算法需要遍历所有的边, 所有我们可以建立一个结构体来存储, 可以不用其他的(邻接表,邻接矩阵)
int bellman_ford() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 这层遍历的意思是:不超过n条边到达n结点
for (int i = 0; i < k; i++) {
// 将dist的上一次结果复制给back
memcpy(back, dist, sizeof dist);
for (int j = 0; j < m; j ++) {
// 这里将back数组代替为原来的dist
dist[s[j].b] = min(back[s[j].a] + s[j].w, dist[s[j].b]);
}
}
return dist[n];
}
int main() {
cin >> n >> m >> k;
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
s[i] = {a, b, w};
}
int t = bellman_ford();
if (t > 0x3f3f3f3f / 2) printf("impossible\n");
else printf("%d\n", t);
return 0;
}
if (t > 0x3f3f3f3f / 2) printf("impossible\n");
// 解释下这句话, 就是说因为有负权边, 所以在dist[n] 虽然从1到不了但是可以从后面到(因为我们是遍历所有的边)
// 如果说后面那几个到n是负边就会使得dist[n]变小, 所以我们用/2 (你可以根据数据范围算一算会不会到/2)
// 比如这题边长为10000(最长边长)*500(这是k) == 5000000 < 0x3f3f3f3f/2 所以可以用
// 或者你写大于5000000也对
7.spfa()算法
那我们来看看如何优化上述代码
- 如果说dist[b]没有变小的话其实这重循环是没有用的
- 所以我们只需要找哪些变小的dist然后遍历这个点的邻居
// st来保存队列中元素是否出现
bool st[N];
int spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 定义一个queue来存储变小的结点
queue<int> que;
// 将第一个结点加入队列
que.push(1);
st[1] = true;
while(que.size()) {
int i = que.fornt();
que.pop();
// 结点i已经出队了
st[i] = false;
遍历所有邻边
if dist[邻边] > dist[i] + i->邻边距离 {
dist[邻边] = dist[i] + i->邻边距离;
// 接下来就是将邻边加入队列,
// 如果邻边不在队列的话就加入, 否则的话就不加入,
// 因为你重复做两次一样的事情是没有必要的, 节约时间, 你不用st数组也可以但是不节约时间
if (!st[邻边]) {
que.push(邻边)
}
}
}
}
ex:
输入样例:
3 3
1 2 5
2 3 -3
1 3 4
输出样例:
2
参考代码:
// 应该都会
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
int n, m;
int spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
st[1] = true;
queue<int> q;
q.push(1);
while(q.size()) {
int 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]) {
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
int main() {
cin >> n >> m;
memset(h, -1 , sizeof h);
while ( m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
if (t > 0x3f3f3f3f / 2) printf("impossible\n");
else printf("%d\n", t);
system("pause");
return 0;
}
负权回路:
a -> b -> c -> d … -> a 这些边权相加<0
如果存在负权回路那么spfa算法会一直进入这个回路导致数据超过2^31- 1 或者time limit error 那么我们肯定要在某个时候停止,那我们就想正权回路和负权回路的区别, 易知:正权回路中边的个数不会超过n - 1(n是结点个数)
// st来保存队列中元素是否出现
bool st[N];
// cnt 数组表示结点边的个数
int cnt[N];
int spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// 定义一个queue来存储变小的结点
queue<int> que;
// 将第一个结点加入队列
que.push(1);
st[1] = true;
while(que.size()) {
int i = que.fornt();
que.pop();
// 结点i已经出队了
st[i] = false;
遍历所有邻边
if dist[邻边] > dist[i] + i->邻边距离 {
dist[邻边] = dist[i] + i->邻边距离;
// 这里就说明i到邻边会有一条边
// 邻边数就等于 i 的边数加1
cnt[邻边] = cnt[i] + 1;
// 邻边数量大于n - 1 了就是有负权回路
if (cnt[邻边] >= n) return true;
// 接下来就是将邻边加入队列,
// 如果邻边不在队列的话就加入, 否则的话就不加入,
// 因为你重复做两次一样的事情是没有必要的, 节约时间, 你不用st数组也可以但是不节约时间
if (!st[邻边]) {
que.push(邻边)
}
}
}
return false;
}
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int h[N], e[N], ne[N], w[N], idx;
int dist[N], cnt[N];
bool st[N];
int n, m;
bool spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
st[1] = true;
queue<int> q;
for (int i = 1; i <= n; i++) {
q.push(i);
}
while(q.size()) {
int 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]) {
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
int main() {
cin >> n >> m;
memset(h, -1 , sizeof h);
while ( m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
system("pause");
return 0;
}
8. floyd(弗洛伊德)算法
- floyd()算法就是a->b的距离如果想要更加段哪必须在经过其他点a->c->b
- 就好比你想从A城市去B城市,最简单的就是直达, 哪有没有更加短的呢,其实就是借助中间城市c, a ->c -> b 就有可能比a->b最短;
// 它可以求多源最短路,q[a][b] a->b的最短路
// floyd() 算法只有一种存储邻接矩阵
for (int i = 0; i < n; i++) {
for (int j = 0;j < n; j ++) {
for (int k = 0; k < n; k ++) {
q[j][k] = min(q[j][k], q[j][i] + q[i][k]);
}
}
}
输入样例:
3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3
输出样例:
impossible
1
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 210, inf = 0x3f3f3f3f;
int q[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 ++) {
q[i][j] = min(q[i][j], q[i][k] + q[k][j]);
}
}
}
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (i == j) q[i][j] = 0;
else q[i][j] = inf;
}
}
while (m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
q[a][b] = min(q[a][b], c);
}
floyd();
while (k --) {
int a, b;
scanf("%d%d", &a, &b);
if (q[a][b] > inf / 2) printf("impossible\n");
else printf("%d\n", q[a][b]);
}
return 0;
}
9.prim() 算法
- prim算法与dijkstra() 算法非常的像
// O(n ^ 2)
// 朴素版的用于稠密图,我们开邻接矩阵
// n 结点数
int dist[N], n;
bool st[N];
int dijkstra() {
// 初始化为inf
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
int ans = 0;
// 循环遍历n - 1 遍
// 因为一次只能加一个点进来, 本来要n 次, 但是最后一次就他自己一个人, 所以无需遍历
for (int i = 0; i < n - 1; i ++ ) {
int t = -1;
for (int j = 1; j <= n; j ++) {
// !st[j] 表示j 不在st里面
// t == -1 or dist[t] > dist[j]
// t == -1其实就是说, 想让t 初始化为不在st中的第一个, 但是你不知道是哪个
// dist[t] > dist[j] 找最小
if (!st[j] && (t == -1 || dist[t] > dist[j])) {
t = j;
}
}
st[t] = true;
// ---------------------
// 这里加一句
ans += dist[t];
// 遍历所有邻边
for (int j = 1; j <= n; j ++) {
// 这一句不一样
// --------------------------------------------------------------------
dist[j] = min(dist[j], dist[t] + t到j的距离);
//-----------------------------------------------------------------------
// prim算法是
dist[j] = min(dist[j], t到j的距离);
}
}
// ----------------
return dist[n];
//-----------------
return ans;
}
- 什么叫最小生成树呢?
ex:
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 520, inf = 0x3f3f3f3f;
int q[N][N];
int n, m;
int dist[N];
bool st[N];
int frim() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
// ans
int res = 0;
// 这里要循环n次而不是n - 1
// dijkstra是优化为n - 1
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;
res += dist[t];
// 这是优化, 可以不写
if (dist[t] == inf) return inf;
for (int j = 1; j <= n; j++) {
dist[j] = min(dist[j], q[t][j]);
}
}
return res;
}
int main() {
cin >> n >> m;
memset(q, 0x3f, sizeof q);
while( m -- ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
// 无向图
q[a][b] = q[b][a] = min(q[a][b], c);
}
int t = frim();
if (t == inf) {
printf("impossible\n");
}else {
printf("%d\n", t);
}
return 0;
}
10.kruskal()算法
也是求最小生成树
// 因为要枚举所有的边, 不妨用结构体来存储图
struct ss {
int a, b, w;
// 按照边权距离从小到大排序
bool operator< (const ss &x)const {
return x.w > w;
}
}s[N];
int ans = 0;
// 并查集, 需要用
int p[N];
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
bool kruskal() {
// 排序
sort(s, s + n);
// cnt -> 集合边个数
int cnt = 0;
// 初始化并查集
for (int i = 1; i <= n; i++) {
p[i] = i;
}
for (int i = 0; i < m; i++) {
// a, b不连通相当于 并查集中a,b不连通, 就需要用并查集的知识
int a = s[i].a, int b = s[i].b, int w = s[i].w;
a = find(a), b = find(b);
// 不在同一个集合
if (a != b) {
// 加入集合
p[a] = b;
// 和prim中ans一样
ans += w;
// 边数加一
cnt++;
}
}
return cnt == n - 1;
}
int main() {
if (kruskal()) {
printf("%d\n", ans);
}else {
没有最小生成树;
}
return 0;
}
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int p[N];
struct ss {
int a, b, w;
bool operator< (const ss &x)const {
return x.w > w;
}
}s[N];
int n, m;
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
int ans;
bool kruskal() {
for (int i = 1; i <= n; i++) p[i] = i;
sort(s, s + m);
int cnt = 0;
for (int i = 0; i < m; i++) {
int a = s[i].a, b = s[i].b , w = s[i].w;
a = find(a), b = find(b);
if (a != b) {
p[a] = b;
ans += w;
cnt++;
}
}
return cnt == n - 1;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
s[i] = {a, b, w};
}
if (kruskal()) {
printf("%d\n", ans);
}else {
printf("impossible\n");
}
system("pause");
return 0;
}
集合
p[a] = b;
// 和prim中ans一样
ans += w;
// 边数加一
cnt++;
}
}
return cnt == n - 1;
}
int main() {
if (kruskal()) {
printf(“%d\n”, ans);
}else {
没有最小生成树;
}
return 0;
}
[外链图片转存中...(img-PhX4EozW-1684478115147)]
输入样例:
4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4
输出样例:
6
参考代码:
```c
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int p[N];
struct ss {
int a, b, w;
bool operator< (const ss &x)const {
return x.w > w;
}
}s[N];
int n, m;
int find(int x) {
if (x != p[x]) p[x] = find(p[x]);
return p[x];
}
int ans;
bool kruskal() {
for (int i = 1; i <= n; i++) p[i] = i;
sort(s, s + m);
int cnt = 0;
for (int i = 0; i < m; i++) {
int a = s[i].a, b = s[i].b , w = s[i].w;
a = find(a), b = find(b);
if (a != b) {
p[a] = b;
ans += w;
cnt++;
}
}
return cnt == n - 1;
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
s[i] = {a, b, w};
}
if (kruskal()) {
printf("%d\n", ans);
}else {
printf("impossible\n");
}
system("pause");
return 0;
}