基础图论算法模板(最短路,最小生成树,二分图)

邻接表示意图:
在这里插入图片描述

有向图的拓扑序列(邻接表)

给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环,请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105
任意边长的绝对值不超过 10000

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10;
int e[N],ne[N],h[N],idx;
int d[N],top[N],cnt=0;
int n,m;
// e,ne,h,idx 邻接表模板
// d 代表每个元素的入度
// top是拓扑排序的序列,cnt代表top中有多少个元素
void add(int a,int b){
    e[idx] = b; ne[idx] = h[a]; h[a] = idx ++;
}
bool topsort(){
    queue<int> q;
    int t;
    for(int i = 1;i <= n; ++i) if(d[i] == 0) q.push(i);
    while(q.size()){
        t = q.front();q.pop();
        top[++cnt] = t;//加入到拓扑序列中       
        for(int i = h[t];i != -1; i = ne[i]){//遍历t点的出边
            int j = e[i];
            d[j] --;
            if(d[j] == 0) q.push(j);
        }
    }
    if(cnt < n) return 0;
    //如果拓扑序列存在,那么所有点都会被加到拓扑序列里面 
    if(cnt == n)return 1;
}
int main(){
    cin >> n >> m;
    memset(h,-1,sizeof h);
    int a,b;
    while(m--){
        cin >> a >> b;
        add(a,b); 
        d[b] ++;//a->b,b的入度++
    }
    if(topsort() == 0) cout << "-1";
    else {
        for(int i = 1;i <= n; ++i)
            cout << top[i] <<" ";
    }
    return 0;
}

最短路

  • n:点数,m:边数
  • 稠密图(边多):朴素Dijkstra,邻接矩阵即可
  • 稀疏图(边少):堆优化Dijkstra
    在这里插入图片描述

在这里插入图片描述

Dijkstra-朴素 O ( n 2 ) O(n^2) O(n2)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1
1 ≤ n ≤ 500 1≤n≤500 1n500
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1m105
任意边长的绝对值不超过 10000

  • 在最短路里,如果有>0的自环,这个自环显然不会出现在最短路里面
  • 在Dijkstra里面,如果有>=0的自环,在松弛操作时没有影响,所以不用管

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxv=505;
int n,m,vis[maxv],d[maxv];
int g[maxv][maxv];
int dij(){
    memset(d,0x3f,sizeof d);//便于接下来的操作
    d[1]=0;//确定起点到自己的距离为0
    for(int i=1;i<=n;i++){
        int t=-1;
        for(int j=1;j<=n;j++)
            if(!vis[j] && (t==-1||d[t]>d[j]))t=j;       
        vis[t]=1;
        for(int j=1;j<=n;j++)
            d[j]=min(d[j],d[t]+g[t][j]);//松弛操作
    }
    if(d[n]==0x3f3f3f3f)return -1;
    else return d[n];
}
int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    int a,b,c;
    while(m--){
        scanf("%d%d%d",&a,&b,&c);
        g[a][b]=min(g[a][b],c);//重边取min即可
    }
    int cnt=dij();
    printf("%d",cnt); 
}

Dijkstra-堆优化 O ( m l o g m ) O(mlogm) O(mlogm)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1
1 ≤ n , m ≤ 1.5 ∗ 1 0 5 1≤n,m≤1.5*10^5 1n,m1.5105
任意边长的绝对值不超过 10000

代码:

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 150010;
//稀疏图用邻接表来存
int h[N], e[N], ne[N], idx;
int w[N]; //存权重
int d[N];
bool st[N]; //true说明这个点的最短路径已经确定
int n, m;
//>=0的自环在松弛的时候一定不满足条件,会自动失效 
//有重边也不要紧,更新多次后等价于自动筛出了最短重边
void add(int x, int y, int c){
    w[idx] = c; e[idx] = y; ne[idx] = h[x]; h[x] = idx++;
}
int dijkstra(){
    memset(d, 0x3f, sizeof(d));
    d[1] = 0;
    priority_queue<PII> heap;//大根堆
    heap.push({ 0, 1 });//顺序不能倒,这里显然要根据距离排序
    while(heap.size()){
        PII k = heap.top();heap.pop();//取不在集合S中距离最短的点
        int ver = k.second, dis = -k.first;
        //如果这个点已经出来过了,说明这个点是冗余备份,舍弃即可 
        if(st[ver]) continue;
        st[ver] = true;//放入集合S中,代表这个源点到这个点的最短路已经确定
        for(int i = h[ver]; i != -1; i = ne[i]){
            int j = e[i];//i代表ver点的一个出边,e是这个边的终点
            if(d[j] > dis + w[i]){
                d[j] = dis + w[i];
                heap.push({-d[j], j});
            }
        }
    }
    if(d[n] == 0x3f3f3f3f) return -1;
    else return d[n];
}
int main(){
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    while(m--){
        int x, y, c;
        scanf("%d%d%d", &x, &y, &c);
        add(x, y, c);
    }
    cout << dijkstra() << endl;
    return 0;
}

习题:

洛谷 P1629 邮递员送信

题意:

给定 P 个点, Q条有向边,经过每条边都有一个花费,现在需要先将P-1个人从1号点分别送往P-1个点上(即除1号点外其他每一个点上都必须有一个人),然后在将这P-1个人送回1号点,问最少的总花费

思路:

思维活跃一些 , 正反建图各跑一遍就好啦

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
#define ll long long
const int N=1e6+5,M=1e6+5;
int n,m;

inline ll in() { char ch = getchar();ll x = 0, f = 1;while (ch<'0' || ch>'9') { if (ch == '-')f = -1;ch = getchar(); }while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0';ch = getchar(); }return x * f; }

int Head[N],ver[M],Next[M],edge[M],tot=1;
int Head1[N],ver1[M],Next1[M],edge1[M],tot1=1;
void add(int x, int y, int z){
    //建正图
    tot++;
    ver[tot]=y;edge[tot]=z;Next[tot]=Head[x];Head[x]=tot;
    //建反图
    tot1++;
    ver1[tot1]=x;edge1[tot1]=z;Next1[tot1]=Head1[y];Head1[y]=tot1;
}

int dis[N],dis1[N];
bool vis[N];
priority_queue<pair<int,int> >q;

void dijstra1(){
    memset(dis,0x3f,sizeof(dis));
    while(q.size())q.pop();
    q.push(make_pair(0,1));
    dis[1]=0;
    while(q.size()){
        int u=q.top().second;
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i=Head[u];i;i=Next[i]){
            int v=ver[i];
            if(dis[v]>dis[u]+edge[i]){
                dis[v]=dis[u]+edge[i];
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}

void dijstra2(){
    memset(dis1,0x3f,sizeof(dis1));
    memset(vis,0,sizeof(vis));
    while(q.size())q.pop();
    q.push(make_pair(0,1));
    dis1[1]=0;
    while(q.size()){
        int u=q.top().second;
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i=Head1[u];i;i=Next1[i]){
            int v=ver1[i];
            if(dis1[v]>dis1[u]+edge1[i]){
                dis1[v]=dis1[u]+edge1[i];
                q.push(make_pair(-dis1[v],v));
            }
        }
    }

}
int main(){
    int t=1;
    while (t--)
    {
        memset(vis,0,sizeof vis);
        memset(Head,0,sizeof Head);
        memset(Head1,0,sizeof Head1);
        tot=1,tot1=1;
        n=in();m=in();
        for(int i=1;i<=m;i++){
            int x=in(),y=in(),z=in();
            add(x, y, z);
        }
        dijstra1();
        dijstra2();
        int ans=0;
        for(int i=2;i<=n;i++)ans+=dis[i]+dis1[i];
        cout<<ans<<endl;
    }
}

Bellman_ford O ( n m ) O(nm) O(nm)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 impossible,注意:图中可能存在负权回路 。
1 ≤ n , k ≤ 500 1≤n,k≤500 1n,k500
1 ≤ m ≤ 10000 1≤m≤10000 1m10000
任意边长的绝对值不超过 10000

注:

在下面代码中,判断从1号点走到n号点否是无穷大距离时,需要进行if(t > INF/2)判断,而并非是if(t == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,t大于某个与INF相同数量级的数即可

代码:

#include<iostream>
#include<cstring>
using namespace std;
const int N = 510, M = 10010;
struct Edge{
    int a,b,w;
} e[M];//边
int dist[N];
int back[N];//备份数组防止串联
int n, m, k;//k代表最短路径最多包涵k条边

int bellman_ford(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;//初始化,注意别忘了 
    for (int i = 0; i < k; i++) {//最多经过k条边
        memcpy(back, dist, sizeof dist);
        for (int j = 0; j < m; j++) {//遍历所有边,注意循环节是m
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);
            //使用backup:避免给a更新后立马更新b
        }
    }
    //可能存在 ∞把 ∞更新的情况
    if (dist[n] > 0x3f3f3f3f / 2) return -1;
    else return dist[n];
}

int main(){
    scanf("%d%d%d", &n, &m, &k);
    int a, b, w;
    for (int i = 0; i < m; i++){
        scanf("%d%d%d", &a, &b, &w);
        //傻瓜式存图,边与边之间可以没有任何关联 
        e[i] = {a, b, w};
    }
    int res = bellman_ford();
    if(res == -1) puts("impossible");
    else cout << res;
    return 0;
}

Spfa O ( k m ) O(km) O(km)~ O ( n m ) O(nm) O(nm)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 impossible,数据保证不存在负权回路。
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105
图中涉及边长绝对值均不超过 10000

  • spfa对bellman—ford中对所有边进行松弛操作进行了优化,原因是在bellman—ford中,即使该点的最短距离尚未更新过,但还是需要用尚未更新过的值去更新其他点,由此可知,该操作是不必要的,我们只需要找到更新过的值去更新其他点即可
  • spfa也能解决权值为正的图的最短距离问题,且一般(不被卡)的情况下比Dijkstra算法还好

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 1e5+10;
int n, m;  // n个点m条边
int h[N], e[N], w[N], ne[N], idx;  // 邻接表存储所有边
int dist[N]; // 存储1号点到每个点的最短距离
bool st[N];//spfa核心,存储每个点是否在队列中
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;//1号点在队列里 
    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];//更新到j点的最短距离
                if(!st[j]){//如果不在队列里
                    q.push(j);st[j] = true;
                }
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f)return -1;// 1 ~ n不连通,返回-1
    return dist[n];//返回第n个点到源点的最短距离
}

int main(){
    cin >> n >> m;
    memset(h, -1, sizeof h);//初始化所有表头
    int a, b, c;
    while(m--){       
        cin >> a >> b >> c;
        add(a, b, c);
    }
    int t = spfa();
    if(t == -1) puts("impossible");
    else cout << t << endl;
    return 0;
}

spfa判断负环 O ( k m ) O(km) O(km)~ O ( n m ) O(nm) O(nm)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, 边权可能为负数,请你判断图中是否存在负权回路,如果图中存在负权回路,则输出 Yes,否则输出 No
1 ≤ n ≤ 2000 1≤n≤2000 1n2000
1 ≤ m ≤ 10000 1≤m≤10000 1m10000
图中涉及边长绝对值均不超过 10000

每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时可以将所有点插入队列中

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=2010,M=10010;
int h[N],e[M],ne[M],w[M],idx;
bool st[N];
int d[N],cnt[N];//cnt数组表示到达当前这个点最短路的边数
int n,m;
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa(){
    memset(d,0,sizeof(d));//本题可以不做初始化
    memset(cnt,0,sizeof(cnt));    
    memset(st,false,sizeof(st));
    queue<int> q;
    for(int i=1;i<=n;i++){//判整个图的负环要将每个节点都加入
        st[i]=true;q.push(i);
    }
    while(!q.empty()){
        int t=q.front();q.pop();
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(d[j]>d[t]+w[i]){
                d[j]=d[t]+w[i];
                cnt[j]=cnt[t]+1;

                if(cnt[j]>=n) return true;
                
                if(!st[j]){
                    st[j]=true;q.push(j);
                }
            }
        }
    }
    return false;
}
int main(){
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    if(spfa()) puts("Yes");
    else puts("No");
}

Floyd O ( n 3 ) O(n^3) O(n3)

给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数,再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出impossible,数据保证图中不存在负权回路
1 ≤ n ≤ 200 1≤n≤200 1n200
1 ≤ k ≤ n 2 1≤k≤n^2 1kn2
1 ≤ m ≤ 20000 1≤m≤20000 1m20000
图中涉及边长绝对值均不超过 10000

思路:

f [ i , j , k ] f[i, j, k] f[i,j,k]表示从 i 走到 j 的路径上除 i 和 j 点外只经过1到 k 的点的所有路径的最短距离。那么 f [ i , j , k ] = m i n ( f [ i , j , k − 1 ) , f [ i , k , k − 1 ] + f [ k , j , k − 1 ] f[i, j, k] = min(f[i, j, k - 1), f[i, k, k - 1] + f[k, j, k - 1] f[i,j,k]=min(f[i,j,k1),f[i,k,k1]+f[k,j,k1]
因此在计算第 k 层的 f [ i , j ] f[i, j] f[i,j]的时候必须先将第 k - 1 层的所有状态计算出来,所以需要把 k 放在最外层

判断从a到b是否是无穷大距离时,需要进行if(t > INF/2)判断,而并非是if(t == INF)判断,原因是INF是一个确定的值,并非真正的无穷大,会随着其他数值而受到影响,t大于某个与INF相同数量级的数即可

代码:

#include <iostream>
using namespace std;
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
int d[N][N];

void floyd() {//d[i,j,k]:从i出发,只经过1-k这几个中间点,到j的最短距离
    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() {
    cin >> n >> m >> k;
    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--) {
        cin >> x >> y >> z;
        d[x][y] = min(d[x][y], z);//重边
    }
    floyd();
    while(k--) {
        cin >> x >> y;
        if(d[x][y] > INF/2) puts("impossible");
        //由于有负权边存在所以约大过INF/2也很合理
        else cout << d[x][y] << endl;
    }
    return 0;
}

最小生成树

图片来自acwing:

  • n :点数, m :边数 n:点数,m:边数 n:点数,m:边数
    在这里插入图片描述

朴素Prim O ( n 2 ) O(n^2) O(n2)

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数,求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
1 ≤ n ≤ 500 1≤n≤500 1n500
1 ≤ m ≤ 1 0 5 1≤m≤10^5 1m105
图中涉及边的边权的绝对值均不超过 10000

思路:

S:当前已经在联通块中的所有点的集合
联系:Dijkstra算法是更新到起始点的距离,Prim是更新到集合S的距离

代码:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 510, INF = 0x3f3f3f3f;
int n, m;
int g[N][N], dist[N];//dist存储其他点到S的距离
bool st[N];
int prim() {
    //如果图不连通返回INF, 否则返回res
    memset(dist, INF, sizeof dist);
    int res = 0;
    for(int i = 0; i < n; i++) {
        int t = -1;
        for(int j = 1; j <= n; j++)//寻找离集合S最近的点
            if(!st[j] && (t == -1 || dist[t] > dist[j]))t = j;
        //判断是否连通(有无最小生成树),注意i为1时dist[t]为INF       
        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() {
    cin >> n >> m;
    int u, v, w;

    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            g[i][j] = INF;

    while(m--) {
        cin >> u >> v >> w;
        g[u][v] = g[v][u] = min(g[u][v], w);
    }
    int t = prim();//临时存储防止执行两次函数导致最后仅返回0
    if(t == INF) puts("impossible");
    else cout << t << endl;
}

Kruskal O ( m l o g m ) O(mlogm) O(mlogm)

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数,求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible
1 ≤ n ≤ 1 0 5 1≤n≤10^5 1n105
1 ≤ m ≤ 2 ∗ 1 0 5 1≤m≤2*10^5 1m2105
图中涉及边的边权的绝对值均不超过 1000

思路:

Kruskal算法对应图: 稀疏图

  1. 将所有边按权重从小到大排序共 O ( m l o g m ) O(mlogm) O(mlogm)
  2. 枚举每条边a,b,权重c共O ( m ) (m) (m)
  3. 枚举过程中if(a,b两点不连通){将a,b边加入集合中}
  • 3操作可用并查集实现
  • 需要使用变量cnt来记录加进集合的边数,若cntくn-1表示不能遍历所有点

代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10, INF = 0x3f3f3f3f;
int n, m;
int p[N];

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

int find(int x) {
    return (p[x] == x) ? p[x] : p[x] = find(p[x]);
}

int kruskal() {
    int cnt = 0, res = 0;
    sort(es, es + m);
    for (int i = 1; i <= n; i++) p[i] = i;
    for (int i = 0; i < m; i++) {
        int a = es[i].a, b = es[i].b, w = es[i].w;
        a = find(a), b = find(b);
        if (a != b) {
            p[a] = b;
            res += w;
            cnt++;
        }
    }
    if (cnt < n - 1) return INF;
    else return res;
}

int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        es[i] = {a, b, w};
    }
    int t = kruskal();
    if (t == INF) cout << "-1";
    else cout << t;
}

二分图

染色法判定二分图

给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,请你判断这个图是否是二分图,如果给定图是二分图,则输出 Yes,否则输出 No
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105

二分图:

  • 将所有点分成两个集合,使得所有边只出现在集合之间
  • 一定不含有奇数环,可能包含长度为偶数的环, 不一定是连通图
  • 可以用染色法判定

代码:

#include<bits/stdc++.h>
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=1e5+5;
//无向图, 所以最大边数是2倍,否则会WA  
int h[N],ne[N*2],e[N*2],idx=0;
int st[N];
int n,m;

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

int dfs(int x,int t){
    st[x]=t;
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(!st[j]){
            if(!dfs(j,3-t))
                return 0;
        }
        else if(st[j]==t)return 0;
    }
    return 1;
}

int main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    f(i,0,m){
        int a,b;
        cin>>a>>b;
        add(a,b);
        add(b,a);
    }
    int fl=1;
    ff(i,1,n){
        if(!st[i]){
            if(!dfs(i,1)){
                fl=0;
                break;
            }
        }
    }
    if(fl)cout<<"Yes"<<endl;
    else cout<<"No"<<endl;
}

二分图的最大匹配

给定一个二分图,其中左半部包含 n 1 n_{1} n1 个点(编号 1∼ n 1 n_{1} n1),右半部包含 n 2 n_{2} n2 个点(编号 1∼ n 2 n_{2} n2),二分图共包含 m 条边,数据保证任意一条边的两个端点都不可能在同一部分中,请你求出二分图的最大匹配数

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

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

代码:

#include<bits/stdc++.h>
#define f(i,a,b) for(int i=a;i<b;i++)
#define ff(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
const int N=1e5+5;
int e[N],ne[N],h[N],idx=0;
int st[N],match[N];
int n1,n2,m;
void add(int x,int y){
    e[idx]=y,ne[idx]=h[x],h[x]=idx++;
}
bool find(int x){
    for(int i=h[x];~i;i=ne[i]){
        int j=e[i];
        if(!st[j]){
            st[j]=1;
            if(!match[j] || find(match[j])){
                match[j]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main(){
    cin>>n1>>n2>>m;
    memset(h,-1,sizeof h);
    f(i,0,m){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int res=0;
    ff(i,1,n1){
        memset(st,0,sizeof st);
        if(find(i))res++;
    }
    cout<<res<<endl;
}

DAG的最小路径点覆盖

DAG (Directed acyclic graph)即有向无环图

最小路径覆盖:针对一个DAG,用最少条互不相交路径,覆盖所有点(其中互不相交是指点不重复)

结论:最小路径点覆盖(最小路径覆盖) = 总点数 - 最大匹配

最小路径重复点覆盖:在最小路径覆盖问题的基础上,去掉互不相交(点可以重复了)

结论:记原图G,求传递闭包后的图G’,则G的最小路径重复点覆盖=G’的最小路径覆盖

题目:

输入数据的第一行是两个整数 N 和 M,接下来 M 行,每行两个整数 x,y,表示一条从 x 到 y 的有向道路,输出一个整数,即最小路径重复点覆盖个数
N ≤ 200 , M ≤ 30000 N≤200,M≤30000 N200,M30000

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e2+5;
int n, m;
int match[N], res;
bool st[N], g[N][N];

bool dfs(int x) {
    for (int i = 1; i <= n; i ++) {
        if (g[x][i] && !st[i]) {
            st[i] = 1;
            if (match[i] == -1 || dfs(match[i])) {
                match[i] = x;
                return 1;
            }
        }
    }
    return 0;
}

int main() {
    memset(match, -1, sizeof match);
    cin >> n >> m;
    while (m --) {
    	int u, v;
        cin >> u >> v;
        g[u][v] = 1;
    }
    
    for (int k = 1; k <= n; k ++)
        for (int i = 1; i <= n; i ++)
            for (int j = 1; j <= n; j ++)
                g[i][j] |= g[i][k] & g[k][j];
                
    for (int i = 1; i <= n; i ++) {
        memset(st, 0, sizeof st);
        if (dfs(i)) res ++;
    }
    cout << n - res << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值