算法竞赛备赛之搜索与图论训练提升,暑期集训营培训

目录

1.DFS和BFS

1.1.DFS深度优先搜索

1.2.BFS广度优先搜索

2.树与图的遍历:拓扑排序

3.最短路

 3.1.迪杰斯特拉算法

3.2.贝尔曼算法

3.3.SPFA算法

3.4.多源汇最短路Floy算法

4.最小生成树

4.1.普利姆算法

4.2.克鲁斯卡尔算法

5.二分图:染色法,匈牙利算法

5.1.染色法

5.2.匈牙利算法


1.DFS和BFS

1.1.DFS深度优先搜索

深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树或图的算法。它从起点开始,沿着一个路径一直到达最深的节点,然后回溯到之前的节点,继续探索下一个路径,直到所有的节点都被访问过。

DFS使用一个来存储待访问的节点,它会将起点压入栈中,然后重复执行以下步骤直到栈为空:

  1. 弹出栈顶节点。

  2. 如果该节点是目标节点,则搜索结束。

  3. 否则将该节点标记为已访问,并将其所有未访问的邻居节点按照某种规则(如按字母表顺序)依次压入栈中。

使用DFS的优点是它的空间复杂度相对较小,因为它只需要存储一条路径上的节点。但是,如果搜索的图或树非常大,DFS可能会陷入死循环或者长时间运行。此外,DFS不一定能找到最优解,因为它只探索了一条路径,而不是所有可能的路径。

因此,在实际应用中,需要根据具体问题的要求选择合适的搜索算法。例如,如果需要找到最短路径,可能更适合使用广度优先搜索(Breadth-First Search, BFS)算法。

842.排列数字

给定一个整数n,将数字1~n排成一排,将会有很多排列方法。

现在,请你按照字典序将所有的排列方法输出。

#include<iostream>
using namespace std;
​
const int N = 7;
​
int n;
int path[N];
bool st[N];
​
void dfs(int u)
{if(u == n){for(int i = 0;i < n; i++)printf("%d ", path[i]);printf("\n");return;}for(int i = 1;i <= n; i++){if(!st[i]){path[u] = i;st[i] = true;dfs(u+1);st[i] = false;}}
}
​
int main()
{scanf("%d", &n);dfs(0);return 0;
}

843.n-皇后问题

n-皇后问题是指将n个皇后放在n-n的国际棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

现在给定整数n,请你输出所有的满足条件的棋盘摆法。

法1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 20;
​
int n;
char g[N][N];
bool col[N], dg[N], udg[N];
​
void dfs(int u)
{if(u == n){for(int i = 0;i < n; i++)puts(g[i]);printf("\n");return;}fot(int i = 1;i <= n; i++){if(!col[i] && !dg[u + i] && !udg[n - u + i]){path[u] = i;col[i] = dg[u + i] = udg[n - u + i] = true;dfs(u+1);col[i] = dg[u + i] = udg[n - u + i] = false;g[u][i] = '·';}}
}
​
int main()
{scanf("%d", &n);for(int i = 0;i < n; i++){for(int j = 0;j < n; j++)g[i][j] = '·';}dfs(0);return 0;
}

法2

#include<iostream>
using namespace std;
​
const int N = 20;
​
int n;
char g[N][N];
bool row[N], col[N], dg[N * 2], udg[N * 2];
​
void dfs(int x, int y, int s)
{if(y == n){y = 0;x++;}if(x == n){if(s == n){for(int i = 0;i < n; i++)puts(g[i]);puts("");}return;}//不放皇后dfs(x, y+1, s);//放皇后if(!row[x] && !col[y] && !dg[x+y] && !udg[x-y+n]){g[x][y] = 'Q';row[x] = col[y] = dg[x+y] = udg[x-y+n] = true;dfs(x, y+1, s+1);row[x] = col[y] = dg[x+y] = udg[x-y+n] = false;g[x][y] = '.';}
}
​
int main()
{scanf("%d", &n);for(int i = 0;i < n; i++){for(int j = 0;j < n; j++){g[i][j] = '.';}}dfs(0, 0, 0);return 0;
}

1.2.BFS广度优先搜索

广度优先搜索(Breadth-First Search, BFS)是一种用于遍历或搜索树或图的算法。它从起点开始,首先访问所有与起点直接相邻的节点,然后访问这些节点的相邻节点,以此类推,直到所有的节点都被访问过。

BFS使用一个队列来存储待访问的节点,它会将起点压入队列中,然后重复执行以下步骤直到队列为空:

  1. 弹出队列首部节点。

  2. 如果该节点是目标节点,则搜索结束。

  3. 否则将该节点标记为已访问,并将其所有未访问的邻居节点按照某种规则(如按字母表顺序)依次压入队列中。

使用BFS的优点是它能够保证在最少的时间内找到目标节点,因为它是按照距离从起点由近到远进行搜索的。此外,BFS也能够处理有向无环图(DAG)和图的情况。但是,如果搜索的图或树非常大,BFS可能需要较大的空间来存储队列中的节点,因此空间复杂度较大。

因此,在实际应用中,需要根据具体问题的要求选择合适的搜索算法。例如,如果需要找到深度优先搜索的最短路径,可能更适合使用深度优先搜索算法。

844.走迷宫

给定一个n*m的二维整数数组,用来表示一个迷宫,数组中只包含0成1,其中0表示可以走的路,表示不可通过的墙壁。最初,有一个人位于左上角(1, 1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

请问,该人从左上角移动至右下角(n, m)处,至少需要移动多少次。

数据保证(1, 1)处和(n, m)处的数字为0,且一定至少存在1条通路。

程序代码1

#include<iostream>
#include<queue>
#include<cstring>
​
using namespace std;
​
const int N = 110;
​
typedef pair<int, int> PII;
​
int g[N][N];
int d[N][N];
int n, m;
​
int bfs()
{queue<pair<int, int>> q;q.push({0, 0});memset(d, -1, sizeof(d));d[0][0] = 0;int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};while(q.size()){PII t = q.front();q.pop();for(int i = 0;i < 4; i++){int x = t.first + dx[i], y = t.second + dy[i];if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){d[x][y] = d[t.first][t.second] + 1;q.push({x, y});}}}return d[n-1][m-1];
}
​
int main()
{scanf("%d%d", &n, &m);for(int i = 0;i < n; i++)for(int j = 0;j < m; j++)scanf("%d", &g[i][j]);cout << bfs() << endl;return 0;
}

打印路径代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
​
typedef pair<int, int> PII;
const int N = 110;
​
int n, m;
int g[N][N];
int d[N][N];
int prev[N][N];
PII q[N * N];
​
int bfs()
{int hh = 0, tt = 0;q[0] = {0, 0};memset(d, -1, sizeof(d));d[0][0] = 0;int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};while(hh <= tt){auto t = q[hh++];for(int i = 0;i < 4; i++){int x = t.first + dx[i], y = t.second + dy[i];if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == 0 && d[x][y] == -1){d[x][y] = d[t.first][t.second] + 1;prev[x][y] = t;q[++tt] = {x, y};}}}int x = n - 1, y = m - 1;while(x || y){cout << x << ' ' << y << endl;auto t = prev[x][y];x = t.first, y = t.second;}return d[n-1][m-1];
}
​
int main()
{scanf("%d%d", &n, &m);for(int i = 0;i < n; i++){for(int j = 0;j < m; j++){scanf("%d", g[i][j]);}}cout << bfs() << endl;return 0;
}

5 5

0 1 0 0 0

0 1 0 1 0

0 0 0 0 0

0 1 1 1 0

0 0 0 1 0

八数码

树是一种特殊的图,属于无环图。

图分为有向图和无向图。

846.树的重心

给定一棵树,树中包含n个结点(编号1~n)和n-1条无向边。

请你找出树的重心,并输出将重心删除后剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个节点,如果将这个结点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

代码

#include<iostream>
using namespace std;
​
const int N = 100010, M = N * 2;
​
int n, m;
int h[N], e[M], ne[M], idx;
bool st[N];
int ans = N;
​
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}
​
int dfs(int u)
{st[u] = true;//标记一下int sum = 0, res = 0;for(int i = h[u];i != -1; i = ne[i]){int j = e[i];if(!st[j]){int s = dfs(j);res = max(res, s);sum += s;}}res = max(res, n - sum - 1);ans = min(ans, res);return sum + 1;
}
​
int main()
{scanf("%d", &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;
}

847.图中点的层次

给定一个n个点m条边的有向图,图中可能存在重边和自环。

所有边的长度都是1,点的编号为1~n。

请你求出1号点到n点的最短距离,如果从1号点无法走到n号点,输出-1。

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 1e5 + 10;
​
int n, m;
int h[N], e[N], ne[N], idx;
int d[N], q[N];
​
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}
​
int bfs()
{int hh = 0, tt = 0;q[0] = 1;memset(d, -1, sizeof(d));d[1] = 0;while(hh <= tt){int t = q[hh++];for(int i = h[t];i != -1; i = ne[i]){int j = e[i];if(d[j] == -1){d[j] = d[t] + 1;q[++tt] = j;}}}return d[n];
}
​
int main()
{cin >> n >> m;memset(h, -1, sizeof(h));for(int i = 0;i < m; i++){int a, b;cin >> a >> b;add(a, b);}cout << bfs() << endl;return 0;
}

2.树与图的遍历:拓扑排序

848.有向图的拓扑序列

给定一个n个点m条边的有向图,图中可能存在重边和自环。

请输入任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出-1.

若一个由图中所有点构成的序列A满足:对于图中的每一条边(x, y),x在A中都有出现在y之前,则称A是该图的一个拓扑序列。

有向无环图——拓扑图

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 1e5 + 10;
​
int n, m;
int h[N], e[N], ne[N], idx;
int q[N], d[N];
​
void add(int a, int b)
{e[idx] = b;ne[idx] = h[a];h[a] = idx++;
}
​
bool topsort()
{int hh = 0, tt = 0;for(int i = 1;i <= n; i++){if(!d[i])q[++tt] = i;}while(hh <= tt){int t = q[hh++];for(int i = h[t];i != -1; i = ne[i]){int j = e[t];d[j]--;if(d[j] == 0){q[++tt] = j;}}}return tt == n - 1;
}
​
int main()
{cin >> n >> m;memset(h, -1, sizeof(h));for(int i = 0;i < n; i++){int a, b;cin >> a >> b;add(a, b);d[b]++;}if(topsort()){for(int i = 0;i < n; i++)printf("%d ", q[i]);puts("");}elseputs("-1");cout << << endl;return 0;
}

3.最短路

最短路问题是在给定的有向图或无向图中找到从起点到终点的最短路径的问题。这个问题在计算机科学和应用数学中有着广泛的应用,例如在路由算法、交通控制和电路设计中都需要解决最短路问题。

解决最短路问题的方法主要有两种:Dijkstra算法和Bellman-Ford算法。

Dijkstra算法是一种贪心算法,它基于图论的原理,通过不断更新起点到各个节点的最短距离,最终求得起点到终点的最短路径。该算法的基本思路是从起点开始,每次选择一个距离起点最近的节点,并更新起点到各个节点的距离。通过不断重复这个过程,最终可以得到起点到终点的最短路径。

Bellman-Ford算法是一种动态规划算法,它可以处理带有负权边的图。该算法的基本思路是通过对所有节点进行多次松弛操作,逐步更新起点到各个节点的最短距离。在每次松弛操作中,该算法会检查是否存在一个节点的距离可以通过其他节点的路径得到更短的距离,如果有,则更新该节点的距离。通过多次松弛操作,该算法可以找到起点到终点的最短路径。

除了这两种算法,还有其他一些解决最短路问题的方法,例如Floyd-Warshall算法和A*算法等。不同的算法适用于不同类型的图和不同的应用场景,需要根据具体情况选择合适的算法。

uTools_1689583511271

 3.1.迪杰斯特拉算法

Dijkstra算法是一种用于解决最短路径问题的算法,它可以在带权重的图中找到从一个起点到所有其他节点的最短路径。

该算法的基本思路是从起点开始,每次选择距离最短的节点作为中转点,并将该节点的邻居节点更新距离,直到所有节点都被访问过或者没有可达的节点。

具体实现过程如下:

  1. 初始化:将起点的距离设置为0,其他节点的距离设置为无穷大。

  2. 选择中转点:从起点开始,选择距离起点最近的节点作为中转点,将该节点的距离更新为起点到该节点的距离。

  3. 更新距离:对于中转点周围的邻居节点,如果从起点到该邻居节点的距离比之前的距离更短,则更新该邻居节点的距离为起点到该邻居节点的距离。

  4. 重复步骤2和步骤3,直到所有节点都被访问过或者没有可达的节点。

  5. 输出结果:最终得到从起点到所有节点的最短路径。

Dijkstra算法的时间复杂度为O(n^2),其中n是节点的数量。如果使用堆优化可以将时间复杂度优化为O(m log n),其中m是边数。

849.Dijkstra求最短路I

给定一个n个点m条边的1有向图,图中可能存在重边和自环,所以边权均为正值。

请你求出1号点到n号点的最短距离,如果无法从1号点走到n号点,则输出-1.

3 3

1 2 2

2 3 1

1 3 4

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 510;
​
int n, m;
int g[N][N];
int dist[N][N];
bool st[N];
​
int dijkstra()
{memset(dist, 0x3f, sizeof(dist));dist[1] = 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;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;return dist[n];
}
​
int main()
{cin >> n >> m;memset(g, 0x3f, sizeof(g));while(m--){int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = min(g[a][b], c);}int t = dijkstra();printf("%d\n", t);return 0;
}

Dijkstra求最小短路II

堆优化版dijkstra算法

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
typedef pair<int, int> PII;
​
const int N = 1e5 + 10;
​
int n, m;
int h[N], w[N], e[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 dijkstra()
{memset(dist, 0x3f, sizeof(dist));dist[1] = 0;priority_queue<PII, vector<PII>, greater<PII>> heap;heap.push({0, 1});while(heap.size()){auto t = heap.top();heap.pop();int ver = t.second, distance = t.first;if(st[ver]) continue;for(int i = h[ver];i != -1; i = ne[i]){int j = e[i];if(dist[j] > distance + w[i]){dist[j] = distance + w[i];heap.push({dist[j], j});}}}if(dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
​
int main()
{scanf("%d%d", &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 = dijkstra();printf("%d\n", t);return 0;
}

3.2.贝尔曼算法

Bellman-Ford算法

for循环n次,每一次循环所有边

dist[b] = min(dist[b], dist[a]) 松弛操作

dist[b] ≤ dist[a] + w三角不等式 有负权边就很难成立啦

853.有边数限制的最短路

给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。

请你求出从1号点到n号点的最多经过k条边的最短距离,如果无法从1号点走到n号点,输出impossible。

注意:图中可能存在负权回路。

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 510, M = 10010;
​
int n, m, k;
iny dist[N], backup[N];
​
struct Edge
{int a, b, w;
}edges[M];
​
int bellman_ford()
{memset(dist, 0x3f, sizeof(dist));dist[1] = 0;for(int i = 0;i < k; i++){memcpy(bakcup, dist, sizeof(backup));for(int j = 0;j < m; j++){int a = edges[j].a, b = edges[i].b, w = edges[i].w;dist[b] = min(dist[b], backup[a] + w);}}if(dist[n] > 0x3f3f3f3f / 2) return -1;return dist[n];
}
​
int main()
{scanf("%d%d%d", &n, &m, &k);for(int i = 0;i < m; i++){int a, b, w;scanf("%d%d%d", &a, &b, &w);edges[i] = {a, b, w};}int t = bellman_ford();if(t == -1)puts("impossible");else printf("%d\n", t);return 0;
}

3.3.SPFA算法

851.spfa求最短路

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
​
using namespace std;
​
typedef pair<int, int> PII;
​
const int N = 510, M = 10010;
​
int n, m;
int h[N], w[N], e[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));dist[1] = 0;queue<int> q;q.push(1);st[1] = true;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;}}}}if(dist[n] == 0x3f3f3f3f) return -1;return dist[n];
}
​
int main()
{scanf("%d%d%d", &a, &b, &c);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 == -1)puts("");elseprintf("%d\n", t);return 0;
}

852.spfa判断负环

给定一个n个点m条边的有向图,图中可能存在重边和自环,边权可能为负数。

请你判断图中是否存在负权回路。

3 3

1 2 -1

2 3 4

3 1 -4

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 10010;
​
int n, m;
int h[N], w[N], e[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()
{queue<int> q;q.push(1);st[1] = true;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;
}
​
int main()
{scanf("%d%d", &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(spaf())printf("Yes\n");elseprintf("No\n");return 0;
}

3.4.多源汇最短路Floy算法

for(int k = 1;k <= n; k++)
{for(i = 1;i <= n; i++){for(int j = 1;j <= n; j++){d[i][j] = min(d[i][j], d[i][k] + d[k][j]);}}
}

854.Floyd求最短路

给定一个n个点m条边的有向图,图中可能存在重边和闭环,边权可能为负数。

再给定k个询问,每个询问包含两个整数x和y,表示查询从点x到点y的最短距离,如果路径不存在,则输出“impossible”

数据保证图中不存在负权回路。

#include<iostream>
#include<algorithm>
#include<cstring>
​
using namespace std;
​
const int N = 1e4 + 10;
​
int n, m, Q;
int d[N][N];
​
void floyd()
{for(int k = 1;k <= n; k++){for(int i = 1;i <= n; i++){for(int j = 1;j <= n; j++){d[i][j] = min(d[i][k], d[k][j]);}}}
}
​
int main()
{scanf("%d%d%d", &n, &m, &Q);for(int i = 1;i <= n; i++){for(int j = 1;j <= n; j++){if(i == j)d[i][j] = 0;elsed[i][j] = INF;}}while(m--){int a, b, w;scanf("%d%d%d", &a, &b, &w);d[a][b] = min(d[a][b], w);}floyd();while(Q--){int a, b;scanf("%d%d",&a, &b);if(d[a][b] == INF)puts("impossible");elseprintf("%d\n", d[a][b]);}return 0;
}

4.最小生成树

最小生成树的两个算法:

  1. 普利姆算法(Prim)

  2. 克鲁斯卡尔算法(Kruskal)

4.1.普利姆算法

朴素版Prim,类似于Dijkstra算法

Dijkstra算法的主体部分:

int dijkstra()
{memset(dist, -1, sizeof(dist));dist[1] = 0;for(int i = 0;i < n - 1; i++){int t = -1;for(int j = 1;j <= n; j++){if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;}for(int  j = 1;j <= n; j++){dist[j] = min(dist[j], dist[t] + g[t][j]);}st[t] = true;}if(dist[n] == 0x3f3f3f3f)return -1;return dist[n];
}

prim的算法思路

dist[i] <- 正无穷

for(i = 0;i < n; i++)
{t->找到集合距离最近的点用t更新其他点到集合的距离,st[t] = true;
}

858.Prim算法求最小生成树

给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负。

求最小生成树的树边权重之和,如果最小生成树不存在则输出impossible。

给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=[V],m=[E]

由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和和最小的生成树被称为无向图G的最小连通图。

4 5

1 2 1

1 3 2

1 4 3

2 3 2

3 4 4

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
cosnt int N = 1e4 + 10;
​
int n, m;
int g[N][N];
int dist[N];
bool st[N];
​
int prim()
{memset(dist, 0x3f, sizeof(dist));int res = 0;for(int i = 0;i < n; i++){int t = -1;for(int j = 1;j <= n; j++){if(!st[t] && (t == -1 || dist[t] > dist[j]))t = j;if(i && dist[t] == INF)return INF;if(i)res += dist[t];for(int j = 1;j <= n; j++)dist[j] = min(dist[j], g[t][j]);st[t] = true;}}return true;
}
​
int main()
{scanf("%d%d", &n, &m);memset(g, 0x3f, sizeof(g));while(m--){int a, b, c;scanf("%d%d%d", &a, &b, &c);g[a][b] = g[b][a] = min(g[a][b], c);}int t = prim();if(t == INF)puts("impossible");elseprintf("%d\n", t);return 0;
}

堆优化版Prim

竞赛中实际上用到的不多。

4.2.克鲁斯卡尔算法

Kruskal算法的基本思想:

  1. 将所有边按照权重从小到大排序

  2. 枚举每条边a,b,权重c 如果a,b不连通,就将这条边加入到这个集合里面

859.Kruskal算法求最小生成树

4 5

1 2 1

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 1e4 + 10;
​
int n, m;
int p[N];
​
struct Edge
{int a, b, w;bool operator< (const Edge &W)const{return w < W.w;}
}edges[N];
​
int find(int x)
{if(p[x] != x) p[x] = find(p[x]);return p[x];}
​
int main()
{scanf("%d%d", &n, &m);for(int i = 0;i < m; i++){int a, b, c;scanf("%d%d%d", &a, &b, &c);edges[i] = {a, b, w};}sort(edges, edges + m);for(int i = 1;i <= n; i++)p[i] = i;int res = 0, cnt = 0;for(int i = 0;i < m; i++){int a = edges[i].a, b = edges[i].b, w = edges[i].w;a = find(a), b = find(b);if(a != b){p[a] = b;res += w;cnt++;}}if(cnt < n - 1)puts("impossible");elseprintf("%d\n", res);return 0;
}

5.二分图:染色法,匈牙利算法

二分图当且仅当图中不含奇数环。

二分图又叫二部图,是图论中的一种特殊模型。设G=(V,E)是无向图,如果根据顶点V可分割为两个互不相交的子集(A,B),且图中的每条边(i,j)所关联的两个顶点i和j分属这两个不同的顶点集(i∈A,j∈B),则图G就是一个二分图。简单来说,就是顶点集V可分割为两个互不相交的子集,且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。

二分图有最大匹配和最小匹配问题,在二分图中的一个匹配是指边集中任意两条边都不依附于同一个顶点,极大匹配是指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数,最大匹配是所有极大匹配当中边数最大的一个匹配。

分辨是否是二分图:

  1. 染色法 O(n + m)

  2. 匈牙利算法 O(mn),实际运行时间一般远小于O(mn)

5.1.染色法

二分图染色法是一种判断二分图的方法,可以分为连通图判断和非连通图判断1。

染色思路如下:

  1. 初始所有顶点未染色。

  2. 随意取出一个未染色的顶点u,把它染成一种颜色(假设为0)。

  3. 取与它连接的结点v,如果v未染色,则将v染成和u不同的颜色(假设为1),如果v已经染色,那么判断u和v颜色是否相同,相同则表明染色失败,该图不是二分图,结束。

  4. 遍历所有结点,重复步骤)。

  5. 连通图只需要一次dfs染色,非连通图则多次dfs染色。

for(int i = 1;i <= n; i++)
{if(v未染色)dfs(i);
}

860.染色法判定二分图

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 1e5 + 10, M = 2e5 + 10;
​
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 father, int c)
{color[u] = c;for(int i = h[u];i != -1; i = ne[i]){int j = e[i];if(color[j] == -1){if(!dfs(j, 3 - c)) return false;}else if(color[j] == c) return false;}return true;
}
​
int main()
{scanf("%d%d", &n, &m);memset(h, -1, sizeof(h));while(m--){int a, b;scanf("%d%d", &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) puts("Yes");else puts("No");return 0;
}

题目:关押罪犯

5.2.匈牙利算法

匈牙利算法是一种在多项式时间内求解任务分配问题的组合优化算法,并推动了后来的原始对偶方法。

861.二分图的最大匹配

给定一个二分图,其中左半部包含n1个点(编号1~n1),右半部包含n2个点(编号1 ~n2),二分图共包含m条边。

数据保证任意一条边的两个端点都不可能在同一个部分。

请你求出二分图的最大匹配数。

给定一个二分图G,在G的一个子图M中,M的边集(E)的任意两条边都不依附于同一个顶点,则称为一个匹配。

所有匹配中包含边数最多的一组匹配称为二分图的最大匹配数。

#include<iostream>
#include<cstring>
#include<algorithm>
​
using namespace std;
​
const int N = 510, M = 1e5 + 10;
​
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()
{scanf("%d%d%d", &n1, &n2, &m);memset(h , -1, sizeof(h));while(m--){int a, b;scanf("%d%d", &a, &b);add(a, b);}int res = 0;for(int i = 1; i <= n1; i++){memset(st, false, sizeof(st));if(find(i)) res++;}printf("%d\n", res);return 0;
}

2 2 4

1 1

1 2

2 1

2 2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/40489.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7. CSS(四)

目录 一、浮动 &#xff08;一&#xff09;传统网页布局的三种方式 &#xff08;二&#xff09;标准流&#xff08;普通流/文档流&#xff09; &#xff08;三&#xff09;为什么需要浮动&#xff1f; &#xff08;四&#xff09;什么是浮动 &#xff08;五&#xff09;浮…

OpenAI全球招外包大军,手把手训练ChatGPT取代码农 ; 码农:我自己「杀」自己

目录 前言 OpenAI招了一千多名外包人员&#xff0c;训练AI学会像人类一样一步步思考。如果ChatGPT「学成归来」&#xff0c;码农恐怕真的危了&#xff1f; 码农真的危了&#xff01; 当时OpenAI也说&#xff0c;ChatGPT最合适的定位&#xff0c;应该是编码辅助工具。 用Cha…

计算机竞赛 opencv 图像识别 指纹识别 - python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于机器视觉的指纹识别系统 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c;适…

Vue引入Echarts报错 import * as echarts from “echarts“;

项目场景&#xff1a; 已经下载好echarts cnpm i echarts Vue引入Echarts import echarts from echarts mounted() {this.myChart echarts.init(document.querySelector(.right))this.myChart.setOption({title: {text: 消费列表,left: center},...问题描述 原因分析&#…

【100天精通python】Day38:GUI界面编程_PyQT从入门到实战(中)

目录 专栏导读 4 数据库操作 4.1 连接数据库 4.2 执行 SQL 查询和更新&#xff1a; 4.3 使用模型和视图显示数据 5 多线程编程 5.1 多线程编程的概念和优势 5.2 在 PyQt 中使用多线程 5.3 处理多线程间的同步和通信问题 5.3.1 信号槽机制 5.3.2 线程安全的数据访问 Q…

日常BUG——通过命令行创建vue项目报错

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;日常BUG、BUG、问题分析☀️每日 一言 &#xff1a;存在错误说明你在进步&#xff01; 一、问题描述 在使用vue命令行创建一个vue项目时&#xff0c;出现一下的错误&#xff1a; vue create my…

UDP数据报结构分析(面试重点)

在传输层中有UDP和TCP两个重要的协议&#xff0c;下面将针对UDP数据报的结构进行分析 UDP结构图示 UDP报头结构的分析 UDP报头有4个属性&#xff0c;分别是源端口&#xff0c;目的端口&#xff0c;UDP报文长度&#xff0c;校验和&#xff0c;它们都占16位2个字节&#xff0c;所…

【java面向对象中static关键字】

提纲 static修饰成员变量static修饰成员变量的应用场景static修饰成员方法static修饰成员方法的应用场景static的注意事项static的应用知识&#xff1a;代码块static的应用知识&#xff1a;单例设计模式 static静态的意思&#xff0c;可以修饰成员变量&#xff0c;成员方法&a…

FPGA_学习_14_第一个自写模块的感悟和ila在线调试教程与技巧(寻找APD的击穿偏压)

前一篇博客我们提到了&#xff0c;如果要使用算法找到Vbr&#xff0c;通过寻找APD采集信号的噪声方差的剧变点去寻找Vbr是一个不错的方式。此功能的第一步是在FPGA中实现方差的计算&#xff0c;这个我们已经在上一篇博客中实现了。 继上一篇博客之后&#xff0c;感觉过了很久了…

【Image captioning】ruotianluo/self-critical.pytorch之1—数据集的加载与使用

【Image captioning】ruotianluo/self-critical.pytorch之1—数据集的加载与使用 作者&#xff1a;安静到无声 个人主页 数据加载程序示意图 使用方法 示例代码 #%%from __future__ import absolute_import from __future__ import division from __future__ import print_…

开源,微信小程序 美食便签地图(FoodNoteMap)的设计与开发

目录 0 前言 1 美食便签地图简介 2 美食便签地图小程序端开发 2.1技术选型 2.2前端UI设计 2.3主页界面 2.4个人信息界面 2.5 添加美食界面 2.6美食便签界面 2.8 美食好友界面 2.9 美食圈子界面 2.10 子页面-店铺详情界面 2.11 后台数据缓存 2.12 订阅消息通知 2.1…

探索区块链世界:去中心化应用(DApp)的崭新前景

随着科技的不断发展&#xff0c;区块链技术逐渐引领着数字时代的潮流。在这个充满创新和变革的领域中&#xff0c;去中心化应用&#xff08;DApp&#xff09;成为了备受瞩目的焦点。DApp 不仅改变了传统应用程序的范式&#xff0c;还在金融、社交、游戏等多个领域展现出了广阔的…

GRPC 链接 NODE 和 GOLANG

GRPC 链接 NODE 和 GOLANG GRPC 了解 什么是GRPC gRPC 采用了 Protocol Buffers 作为数据序列化和反序列化的协议&#xff0c;可以更快速地传输数据&#xff0c;并支持多种编程语言的跨平台使用gRPC 提供“统一水平层”来对此类问题进行抽象化。 开发人员在本机平台中编写专…

打造专属照片分享平台:快速上手Piwigo网页搭建

文章目录 通过cpolar分享本地电脑上有趣的照片&#xff1a;部署piwigo网页前言1.Piwigo2. 使用phpstudy网页运行3. 创建网站4. 开始安装Piwogo 总结 &#x1f340;小结&#x1f340; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x…

深度学习1:通过模型评价指标优化训练

P(Positive)表示预测为正样本&#xff0c;N(negative)表示预测为负样本&#xff0c;T(True)表示预测正确,F(False)表示预测错误。 TP&#xff1a;正样本预测正确的数量&#xff08;正确检测&#xff09; FP&#xff1a;负样本预测正确数量&#xff08;误检测&#xff09; TN…

卷积神经网络CNN

卷积神经网络CNN 1 应用领域1 检测任务2 分类和检索3 超分辨率重构4 医学任务5 无人驾驶6 人脸识别 2 卷积的作用3 卷积特征值计算方法4 得到特征图表示5 步长和卷积核大小对结果的影响1 步长2 卷积核 6 边缘填充方法7 特征图尺寸计算与参数共享8 池化层的作用9 整体网络架构10…

【GitLab私有仓库】如何在Linux上用Gitlab搭建自己的私有库并配置cpolar内网穿透?

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xf…

软考笔记——10.项目管理

进度管理 进度管理就是采用科学的方法&#xff0c;确定进度目标&#xff0c;编制进度计划和资源供应计划&#xff0c;进行进度控制&#xff0c;在与质量、成本目标协调的基础上&#xff0c;实现工期目标。 具体来说&#xff0c;包括以下过程&#xff1a; (1) 活动定义&#…

HLS实现FIR低通滤波器+System Generator仿真

硬件&#xff1a;ZYNQ7010 软件&#xff1a;MATLAB 2019b、Vivado 2017.4、HLS 2017.4、System Generator 2017.4 1、MATLAB设计低通滤波器 FPGA系统时钟 50MHz&#xff0c;也是采样频率。用 MATLAB 生成 1MHz 和 10MHz 的正弦波叠加的信号&#xff0c;并量化为 14bit 整数。把…

Web网页浏览器远程访问jupyter notebook服务器【内网穿透】

文章目录 前言1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 前言 Jupyter Notebook&#xff0c;它是一个交互式的数据科学和计算环境&#xff0c;支持多种编程语言&#xff0c;如…