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

news2024/11/24 22:33:59

目录

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("");
    }
    else
        puts("-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("");
    else
        printf("%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");
    else
        printf("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;
            else
                d[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");
        else
            printf("%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");
    else
        printf("%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");
    else
        printf("%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.coloradmin.cn/o/874689.html

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

相关文章

嵌入式学习之strcpy、memset、realloc、malloc使用方法

今天主要针对C语言的strcpy memset realloc mallooc函数进行了学习。 char* strcpy(char* destination,const char* source); void memset ( void *s , char ch, unsigned n )&#xff1b; void* realloc(void* memblock, size_t size)&#xff1b; void *malloc(size_t si…

tkinter打造三维绘图系统,附源代码

文章目录 输入数据加载数据绘图函数源代码 Python绘图系统系列&#xff1a;将matplotlib嵌入到tkinter 简单的绘图系统 数据导入 输入数据 三维绘图需要一个新的坐标变量&#xff0c;设置为z&#xff0c;这个改改UI就可以办到&#xff0c;并不困难。但是&#xff0c;此前用于…

git安装介绍

一、分布式版本控制系统Git概述 1.1 分布式版本控制系统Git介绍 版本控制定义 记录和跟踪项目中各文件内容的改动变化 保存项目的版本历史&#xff0c;以及改动原因&#xff0c;从而让用户能够查看各个历史版本 版本控制系统也是帮助人员进行协作开发的利器 为什么需要版本…

WebRTC音视频通话-WebRTC本地视频通话使用ossrs服务搭建

iOS开发-ossrs服务WebRTC本地视频通话服务搭建 之前开发中使用到了ossrs&#xff0c;这里记录一下ossrs支持的WebRTC本地服务搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c;支持RTM…

福康源:用孝道温暖每一个心灵,共筑幸福健康新人生!

福康源&#xff1a;用孝道温暖每一个心灵&#xff0c;共筑幸福健康新人生 孝道的光芒&#xff1a;福康源的初心 在浮躁的现代社会&#xff0c;孝道的力量正被越来越多的人忽略。然而&#xff0c;福康源的初心却始终坚守孝顺的真谛。孝道不仅是对父母的敬爱&#xff0c;更是一种…

【解析postman工具的使用---基础篇】

postman前端请求详解 主界面1.常见类型的接口请求1.1 查询参数的接口请求1.1.1 什么是查询参数?1.1.2 postman如何请求 1.2 ❤表单类型的接口请求1.2.1 复习下http请求1.2.2❤ 什么是表单 1.3 上传文件的表单请求1.4❤ json类型的接口请求 2. 响应接口数据分析2.1 postman的响…

程序设计 树基础

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

Lorilla LLM - 面向API调用生成的专用AI大模型

Gorilla 是一种先进的大型语言模型 (LLM)&#xff0c;旨在与各种 API 有效交互&#xff0c;从而增强 LLM 在实际应用中的功能。 Gorilla LLM的相关链接&#xff1a;官网 | github | 论文。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、Gorilla LLM简介 通过使用自…

CentOS 项目作出声明,宣称自家 Linux 社区 “始终向所有人开放”

导读在红帽 RHEL 开源事件后&#xff0c;许多兼容 RHEL 的发行版最近都进行了表态&#xff0c;CentOS 项目也在日前作出了声明&#xff0c;宣称自家社区 “始终向所有人开放”。 据悉&#xff0c;CentOS 项目董事会日前在官方博客发布了一则公告&#xff0c;内容主要涉及“ Ce…

拒绝摆烂!C语言练习打卡第一天

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;每日一练 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ &#x1f5d2;️前言&#xff1a; 在前面我们学习完C语言的所以知识&#xff0c;当…

LangChain手记 Models,Prompts and Parsers

整理并翻译自DeepLearning.AILangChain的官方课程&#xff1a;Models,Prompts and Parsers 模型&#xff0c;提示词和解析器&#xff08;Models, Prompts and Parsers&#xff09; 模型&#xff1a;大语言模型提示词&#xff1a;构建传递给模型的输入的方式解析器&#xff1a;…

NAS搭建指南二——影视资源中心

1. 刮削 这一部分是利用 tinyMediaManager 进行影视资源的评分、简介以及图片的搜集工作tinyMediaManager 官方网站地址下载及安装过程&#xff1a;略我的主要修改的设置项如下所示&#xff1a; 使用方法&#xff1a; a. 点击更新媒体库 b. 选择影片–>右键单击–>…

Ubuntu 20.04(服务器版)安装 Anaconda

0、Anaconda介绍 Anaconda是一个开源的Python发行版本&#xff0c;包含了包括Python、Conda、科学计算库等180多个科学包及其依赖项。因此&#xff0c;安装了Anaconda就不用再单独安装CUDA、Python等。 CUDA&#xff0c;在进行深度学习的时候&#xff0c;需要用到GPU&#xf…

生信分析pandas数据处理 Python简明教程 | 视频18

开源生信 Python教程 生信专用简明 Python 文字和视频教程 源码在&#xff1a;https://github.com/Tong-Chen/Bioinfo_course_python 目录 背景介绍 编程开篇为什么学习Python如何安装Python如何运行Python命令和脚本使用什么编辑器写Python脚本Python程序事例Python基本语法 数…

软件开发中常用数据结构介绍:C语言链表

工作之余来写写C语言相关知识&#xff0c;以免忘记。今天就来聊聊C语言链表&#xff0c;我是分享人M哥&#xff0c;目前从事车载控制器的软件开发及测试工作。 学习过程中如有任何疑问&#xff0c;可底下评论&#xff01; 如果觉得文章内容在工作学习中有帮助到你&#xff0c;麻…

干不完根本干不完,我也不想加班,快来围观时间管理大师

时间不够用&#xff0c;怎么办&#xff1f; 成功不靠加班。生产队的驴都不加班&#xff0c;你加什么班&#xff1f;到点就下班&#xff0c;该玩玩&#xff0c;该学习认真学&#xff0c;累了就睡觉。 你可以做任何事&#xff0c;但不必做所有事。 时间管理&#xff0c;不是管…

(十七)大数据实战——Hive的hiveserver2服务安装部署

前言 HiveServer2 是 Apache Hive 的一个服务器端组件&#xff0c;用于支持客户端与 Hive 进行交互和执行查询。HiveServer2服务的作用是提供jdbc/odbc接口&#xff0c;为用户提供远程访问Hive数据的功能。HiveServer2 允许多个客户端同时连接并与 Hive 交互。这些客户端可以通…

HOT91-不同路径

leetcode原题链接&#xff1a;不同路径 题目描述 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;…

shell编程免交互

免交互 定义&#xff1a; 一键执行&#xff0c;无需人工干预&#xff0c;不需要人为控制&#xff0c;就可以完成的操作&#xff0c;继续自动化运维 免交互在linux中的运用&#xff1a; 对于Linux操作系统中&#xff0c;有许多操作都会触及到交互&#xff08;根据系统的指示做…

OpenCV项目开发实战--相机校准—并附实例Python/C++代码实现

介绍 相机标定是计算机视觉中的一项基本任务在 3D 重建、对象跟踪、增强现实和图像分析等各种应用中至关重要。准确的校准通过校正失真和估计相机的内在和外在参数来确保精确的测量和可靠的分析。这本综合指南深入探讨了相机校准的原理、技术和算法。我们探索获取相机的内在和…