Acwing算法基础课图论

Acwing算法基础课图论

题目跳转

DFS

1.排列数字

题目描述

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

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

输入格式

共一行,包含一个整数 n。

输出格式

按字典序输出所有排列方案,每个方案占一行。

数据范围

1≤n≤7

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

提交代码

#include<bits/stdc++.h>
using namespace std;

int n;
bool st[8];
int path[8];


void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0; i < n; i++) cout << path[i] << " ";
        cout << "\n";
        return;
    }
    else
    {
        for(int i = 1; i <= n; i++)
        {
            if(!st[i])
            {
                st[i] = true;
                path[u] = i;
                dfs(u+1);
                st[i] = false;
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    dfs(0);
    return 0;
}

2.n-皇后问题

题目描述

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

1_597ec77c49-8-queens.png

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

输入格式

共一行,包含整数 n。

输出格式

每个解决方案占 n 行,每行输出一个长度为 n 的字符串,用来表示完整的棋盘状态。

其中 . 表示某一个位置的方格状态为空,Q 表示某一个位置的方格上摆着皇后。

每个方案输出完成后,输出一个空行。

注意:行末不能有多余空格。

输出方案的顺序任意,只要不重复且没有遗漏即可。

数据范围

1≤n≤9

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

提交代码

#include<bits/stdc++.h>
using namespace std;


const int N = 20;

int n;
char g[N][N];
bool col[N],dg[N],udg[N];

void init()
{
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            g[i][j]='.';
        }
    }
}


void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0; i < n; i++) cout << g[i] << "\n";
        cout << "\n";
        return;
    }
    else
    {
        for(int i = 0; i < n; i++)
        {
            if(!col[i] && !dg[i + u] && !udg[i - u + n])
            {
                g[u][i] = 'Q';
                col[i] = dg[i + u] = udg[i - u + n] = true;
                dfs(u + 1);
                col[i] = dg[i + u] = udg[i - u + n] = false;
                g[u][i] = '.';
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    init();
    dfs(0);
    return 0;
}

BFS

1.走迷宫

题目描述

给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。

最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。

输出格式

输出一个整数,表示从左上角移动至右下角的最少移动次数。

数据范围

1≤n,m≤100

输入样例:

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

输出样例:

8

提交代码

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N = 105;

typedef pair<int,int> PII;
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
int n, m;
int a[N][N];
vector< vector<int> > d(N,vector<int> (N,-1));
queue<PII> q;

int bfs()
{
    q.push({0,0});
    d[0][0] = 0;
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        for(int i = 0; i < 4; i++)
        {
            int x = t.x + dx[i], y = t.y + dy[i];
            if(x >= n || x < 0 || y >= m || y < 0) continue;
            if(a[x][y] == 1 || d[x][y] != -1) continue;
            q.push({x,y});
            d[x][y] = d[t.x][t.y] + 1;
        }
    }
    return d[n-1][m-1];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < m; j++)
        {
            cin >> a[i][j];
        }
    }
    
    cout << bfs() << "\n";
    return 0;
}

2.八数码

题目描述

在一个 3×33×3 的网格中,1∼8 这 8 个数字和一个 x 恰好不重不漏地分布在这 3×3的网格中。

例如:

1 2 3
x 4 6
7 5 8

在游戏过程中,可以把 x 与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 x

例如,示例中图形就可以通过让 x 先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3   1 2 3   1 2 3   1 2 3
x 4 6   4 x 6   4 5 6   4 5 6
7 5 8   7 5 8   7 x 8   7 8 x

现在,给你一个初始网格,请你求出得到正确排列至少需要进行多少次交换。

输入格式

输入占一行,将 3×3的初始网格描绘出来。

例如,如果初始网格如下所示:

1 2 3 
x 4 6 
7 5 8 

则输入为:1 2 3 x 4 6 7 5 8

输出格式

输出占一行,包含一个整数,表示最少交换次数。

如果不存在解决方案,则输出 −1−1。

输入样例:

2 3 4 1 5 x 7 6 8

输出样例

19

提交代码

#include<bits/stdc++.h>
using namespace std;


int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};

int bfs(string str1)
{
    queue<string> q;
    unordered_map<string,int> d;
    
    q.push(str1);
    d[str1] = 0;
    
    string end = "12345678x";
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        if(t == end) return d[t];
        int dist = d[t];
        
        int k = t.find('x');
        int x = k / 3, y = k % 3;
        for(int i = 0; i < 4; i++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a >= 0 && a < 3 && b >=0 && b < 3)
            {
                swap(t[a * 3 + b],t[k]);
                if(!d.count(t))
                {
                    d[t] = dist + 1;
                    q.push(t);
                }
                swap(t[a * 3 + b],t[k]);
            }
        }
    }
    
    return -1;
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    string str1;
    for(int i = 0; i < 9; i++)
    {
        char s;
        cin >> s;
        str1 += s;
    }
    cout << bfs(str1) << "\n";
    return 0;
}

树与图的遍历

深度优先遍历

1.树的重心

题目描述

给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。

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

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

输入格式

第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出格式

输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

数据范围

1≤n≤\(10^5\)

输入样例

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;
const int N = 2e5 + 10;
int n;
int e[N], ne[N],h[N],idx;
bool st[N];
int ans = N;

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

int dfs(int u)
{
    int res = 0;
    int sum = 1;
    st[u] = true;
    
    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);
    ans = min(ans, res);
    return sum;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    memset(h, -1, sizeof h);
    cin >> n;
    for(int i = 1; i < n; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b); add(b, a);
    }
    
    dfs(1);
    cout << ans << "\n";
    return 0;
}

宽度优先遍历

1.图中点的层次

题目描述

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

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 a 和 b,表示存在一条从 a 走到 b 的长度为 1的边。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

数据范围

1≤n,m≤\(10^5\)

输入样例:

4 5
1 2
2 3
3 4
1 3
1 4

输出样例:

1

提交代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int e[N], ne[N], h[N], idx;
int dist[N];
int q[N];

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

int bfs()
{
    memset(dist, -1, sizeof dist);
    int hh = 0, tt = 0;
    q[tt] = 1;
    dist[1] = 0;
    while(hh <= tt)
    {
        int t = q[hh++];
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] == -1)
            {
                q[++tt] = j;
                dist[j] = dist[t] + 1;
            }
        }
    }
    
    return dist[n];
}





int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    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() << "\n";
    
    return 0;
}

拓扑排序

1.有向图的拓扑序列

题目描述

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

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 x 和 y,表示存在一条从点 x 到点 y 的有向边 (x,y)。

输出格式

共一行,如果存在拓扑序列,则输出任意一个合法的拓扑序列即可。

否则输出 −1−1。

数据范围

1≤n,m≤\(10^5\)

输入样例:

3 3
1 2
2 3
1 3

输出样例:

1 2 3

提交代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

int n, m;
int d[N];
int e[N], ne[N], h[N], idx;
int q[N];

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

bool topsort()
{
    int hh = 0, tt = -1;
    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[i];
            if(--d[j] == 0) q[++tt] = j;
        }
    }
    return tt == n - 1;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    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]++;
    }
    if(!topsort()) cout << "-1";
    else
    {
        for(int i = 0; i < n; i++ ) cout << q[i] << " ";
    }
    return 0;
}

Dijkstra

1.Dijkstra求最短路 I

题目描述

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

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

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n≤500
1≤m≤\(10^5\)
图中涉及边长均不超过10000。

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

提交代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
const int N = 500+10;

int n, m;
vector< vector<int> > g(N,vector<int> (N,INF));
vector<int> dist(N,INF);
bool st[N];


int dijkstra()
{
    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;
    else return dist[n];
    
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    
    for(int i = 0; i < m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        g[u][v] = min(g[u][v],w);
    }
    
    cout << dijkstra() << "\n";
    return 0;
}

2.Dijkstra求最短路II

题目描述

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

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

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,,,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 −1。

数据范围

1≤n,m≤1.5×\(10^5\),
图中涉及边长均不小于 00,且不超过 10000。
数据保证:如果最短路存在,则最短路的长度不超过 \(10^9\)

输入样例:

3 3
1 2 2
2 3 1
1 3 4

输出样例:

3

提交代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define dist first
#define ver second
using namespace std;

const int N = 2e5 + 10;
int n, m;
vector<int> h(N,-1);
int w[N],e[N],ne[N],idx;
vector<int> dist(N,INF);
bool st[N];

typedef pair<int,int> PII;

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

int dijkstra()
{
    dist[1] = 0;
    priority_queue<PII,vector<PII> ,greater<PII> > q;
    q.push({0,1});
    while(q.size())
    {
        auto t = q.top();
        q.pop();
        int ver = t.ver, distance = t.dist;
        if(st[ver]) continue;
        st[ver] = true;
        
        for(int i = h[ver]; i != -1; i = ne[i])
        {
            int v = e[i];
            if(dist[v]  > distance + w[i])
            {
                dist[v] = distance + w[i];
                q.push({dist[v],v});
            }
            
        }
    }
    
    if(dist[n] == INF) return -1;
    return dist[n];
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    for(int i = 0; i <m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }
    cout << dijkstra() << "\n";
 	return 0;
}

Bellman-Ford

Dijkstra不能求解负的边的图的最短路

image-20230212145959419

1.有边数限制的最短路

题目描述

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

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

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

输入格式

第一行包含三个整数 n,m,k,,。

接下来 m 行,每行包含三个整数 x,y,z,,,表示存在一条从点 x 到点 y 的有向边,边长为 z。

点的编号为 1∼n。

输出格式

输出一个整数,表示从 1 号点到 n 号点的最多经过 k 条边的最短距离。

如果不存在满足条件的路径,则输出 impossible

数据范围

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>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 1e6 + 10;
int u[N], v[N], w[N];
vector<int> dist(N,INF), last(N,INF);
int n, m, k;

void Bellman_Ford()
{
   dist[1] = 0;
   for(int i = 0; i < k; i++)
   {
       last = dist;
       for(int j = 0; j < m; j++)
       {
           dist[v[j]] = min(dist[v[j]],last[u[j]] + w[j]);
       }
   }
}


int main()
{
   ios::sync_with_stdio(false);
   cin.tie(nullptr);
   cin >> n >> m >> k;
   for(int i = 0; i < m; i++)
   {
       cin >> u[i] >> v[i] >> w[i];
   }
   Bellman_Ford();
   if(dist[n] > INF / 2)
   {
       cout << "impossible";
   }
   else
   {
       cout << dist[n] << "\n";
   }
   return 0;
}

Spfa

1.spfa求最短路

题目描述

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

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

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

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

输出一个整数,表示 1 号点到 n 号点的最短距离。

如果路径不存在,则输出 impossible

数据范围

1≤n,m≤\(10^5\),
图中涉及边长绝对值均不超过 10000。

输入样例:

3 3
1 2 5
2 3 -3
1 3 4

输出样例:

2

提交代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 1e5 + 10;
int n, m;
vector<int> h(N,-1);
vector<int> dist(N,INF);
int w[N],e[N],ne[N],idx;
bool st[N];

void add(int u, int v, int c)
{
    e[idx] = v, w[idx] = c,ne[idx] = h[u],h[u] = idx++;
}

int spfa()
{
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while(q.size())
    {
        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])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    
    if(dist[n] == 0x3f3f3f3f) return '0';
    return dist[n];
}


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    for(int i = 0; i < m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    if(spfa() == '0') cout << "impossible";
    else cout << spfa() << "\n";
    return 0;
}

2.spfa判断负环

题目描述

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

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

输入格式

第一行包含整数 n 和 m。

接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No

数据范围

1≤n≤2000,
1≤m≤10000,
图中涉及边长绝对值均不超过 10000。

输入样例:

3 3
1 2 -1
2 3 4
3 1 -4

输出样例:

Yes

提交代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int h[N],w[N],e[N],ne[N],idx;
int dist[N],cnt[N];
bool st[N];

void add(int u, int v, int value)
{
    e[idx] = v,w[idx] = value,ne[idx] = h[u],h[u] = idx++;
}

int spfa()
{
    queue<int> q;
    for(int i = 1; i <= n; i++)
    {
        q.push(i);
        st[i] = true;
    }
    
    while(q.size())
    {
        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])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    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);
    }
    
    if(spfa()) cout << "Yes" << "\n";
    else cout << "No" << "\n";
    return 0;
}

Floyd

1.Floyd求最短路

题目描述

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

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

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

输入格式

第一行包含三个整数 n,m,k,,。

接下来 m 行,每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。

接下来 k 行,每行包含两个整数 x,y,表示询问点 x 到点 y 的最短距离。

输出格式

共 k 行,每行输出一个整数,表示询问的结果,若询问两点间不存在路径,则输出 impossible

数据范围

1≤n≤200,
1≤k≤\(n^2\)
1≤m≤20000,
图中涉及边长绝对值均不超过 10000。

输入样例:

3 3 2
1 2 1
2 3 2
1 3 1
2 1
1 3

输出样例:

impossible
1

提交代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 205;
int n, m, q;
int d[N][N];


void init()
{
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(i == j);
            else d[i][j] = INF;
        }
    }
}

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][j],d[i][k] + d[k][j]);
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m >> q;
    
    init();
    for(int i = 0; i < m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = min(d[a][b],c);
    }
    floyd();
    while(q--)
    {
        int e, f;
        cin >> e >> f;
        if(d[e][f] > INF / 2) cout << "impossible\n";
        else cout << d[e][f] << "\n";
    }

    return 0;
}

Prim

1.Prim算法求最小生成树

题目描述

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

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

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1≤n≤500,
1≤m≤\(10^5\),
图中涉及边的边权的绝对值均不超过 10000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

提交代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

int n, m;
const int N = 510;
int dist[N];
int g[N][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[j] && (t == -1 || dist[t] > dist[j])) t = j;
        }
        if(i && dist[t] == INF) return INF;
        if(i) res += dist[t];
        st[t] = true;
        for(int j = 1; j <= n; j++) dist[j] = min(dist[j], g[t][j]);
    }
    
    return res;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    memset(g,0x3f,sizeof g);
    cin >> n >> m;
    while(m--)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    int t = prim();
    if(t != INF) cout << t << "\n";
    else cout << "impossible\n";
    return 0;
}

Kruskal

1.Kruskal算法求最小生成树

题目描述

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

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

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

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

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含三个整数 u,v,w,表示点 u 和点 v 之间存在一条权值为 w 的边。

输出格式

共一行,若存在最小生成树,则输出一个整数,表示最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible

数据范围

1≤n≤\(10^5\),
1≤m≤2∗\(10^5\),
图中涉及边的边权的绝对值均不超过 1000。

输入样例:

4 5
1 2 1
1 3 2
1 4 3
2 3 2
3 4 4

输出样例:

6

提交代码

  • 注意实例化对象dsu时注意参数n,这里必须是n+1,f的下标是从0开始的。
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

const int N = 1e5 + 10;
const int M = 2e5 + 10;

int n, m, res, cnt;
int p[N];

struct Edge
{
    int a, b, w;
    bool operator < (const Edge& X)const
    {
        return w < X.w;
    }
}edges[M];


struct DSU
{
    vector<int> f;
    DSU(int n) :f(n){iota(f.begin(),f.end(),0);}
    int leader(int x)
    {
        while(x != f[x]) x = f[x] = f[f[x]];
        return x;
    }
    
    bool same(int a, int b)
    {
        return leader(a) == leader(b);
    }
    
    bool merge(int u, int v, int w)
    {
        u = leader(u);
        v = leader(v);
        if(u == v) return false;
        f[u] = v;
        res += w;
        cnt++;
        return true;
    }
};

int kruskal()
{
    sort(edges, edges + m);
    DSU dsu(N);
    res = 0; 
    cnt = 0;
    for(int i = 0; i < m; i++)
    {
        int a = edges[i].a;
        int b = edges[i].b;
        int w = edges[i].w;
        dsu.merge(a, b, w);
    }
    
    if(cnt < n - 1) return INF;
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> m;
    for(int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edges[i] ={a,b,w};
    }
    
    int t = kruskal();
    if(t == INF) cout << "impossible\n";
    else cout << t << "\n";
    return 0;
}

二分图

1.染色法判定二分图

题目描述

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

请你判断这个图是否是二分图。

输入格式

第一行包含两个整数 n 和 m。

接下来 m 行,每行包含两个整数 u和 v,表示点 u 和点 v 之间存在一条边。

输出格式

如果给定图是二分图,则输出 Yes,否则输出 No

数据范围

1≤n,m≤\(10^5\)

输入样例:

4 4
1 3
1 4
2 3
2 4

输出样例:

Yes

提交代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, m;
int h[N],e[N], ne[N],idx;
int color[N];

void add(int u, int v)
{
    e[idx] = v, ne[idx] = h[u], h[u] = 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] == -1)
        {
            if(!dfs(j, !c)) return false;
        }
        else if( color[j] == c) return false;
    }
    return true;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    memset(h, -1, sizeof h);
    cin >> n >> m;
    
    for(int i = 0; i < m; i++)
    {
        int u,v;
        cin >> u >> v;
        add(u,v), add(v,u);
    }
    int flag = 1;
    memset(color, -1, sizeof color);
    for(int i = 1; i <= n; i++)
    {
        if(color[i] == -1)
        {
            if(!dfs(i, 0))
            {
                flag = 0;
                break;
            }
        }
    }
    if(flag) puts("Yes");
    else puts("No");
    return 0;
}

2.二分图的最大匹配

题目描述

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

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

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

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

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

输入格式

第一行包含三个整数 n1、 n2 和 m。

接下来 m 行,每行包含两个整数 u 和 v,表示左半部点集中的点 u 和右半部点集中的点 v 之间存在一条边。

输出格式

输出一个整数,表示二分图的最大匹配数。

数据范围

1≤n1,n2≤50
1≤u≤n1,
1≤v≤n2,
1≤m≤\(10^5\)

输入样例:

2 2 4
1 1
1 2
2 1
2 2

输出样例:

2

提交代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n1, n2, m;
int h[N], e[N], ne[N], idx;
int match[N];
bool st[N];

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

int 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()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    memset(h, -1, sizeof h);
    cin >> n1 >> n2 >> m;
    
    for(int i = 0; i < m; i++)
    {
        int u, v;
        cin >> u >> v;
        add(u, v);//切忌不要add(v, u);
    }
    int res = 0;
    for(int i = 1; i <= n1; i++)
    {
        memset(st, 0 ,sizeof st);
        if(find(i)) res++;
    }
    cout << res << "\n";
    
    return 0;
}
posted @ 2023-02-15 13:15  哲远甄骏  阅读(19)  评论(0)    收藏  举报