最短路 二分图 模板

时间:2019-09-21
本文章向大家介绍最短路 二分图 模板,主要包括最短路 二分图 模板使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

最小生成树 (正边,负边都可以)

Prim 朴素版 O(n^2)

  • 稠密图,代码短,跟Dij很相似

算法流程(以点来扩展):
初始化dist [i] 为 正无穷

for i 0 ~ n

​ 先集合外距离最近的点 赋值给 t

​ 用 t 更新 其他点到 集合 的距离

​ st[t] = true;

​ * 其中 ,点 到 集合的距离,定义为 ,集合外一点,到集合内的点的距离,最小的那条边

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int g[N][N],dist[N],n,m;
bool st[N];
int prim(){
    int ret = 0;
    memset(dist,0x3f,sizeof dist);
    for(int i = 0;i < n; ++i){
        int t = -1;
        for(int j = 1;j <= n; ++j){ // 选取集合外一点,到集合内点距离最小的点
            if(!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }
        st[t] = 1;
        if(i && dist[t] == INF) return -1; // 如果 当前选的最小的点为INF,说明不连通,第一次除外
        // 因为 第一次 集合中没有元素
        if(i) ret += dist[t];// 除了第一次,之后的每一次,dist t 都代表一条边
        // 先累加,在更新,(不然就会被 负的自环搞崩, 例如  1-1 ,权值为 -10)
        for(int j = 1;j <= n; ++j) dist[j] = min(dist[j],g[t][j]); // 因为 t 已经被选到集合中去了
        // 这个for的意思就是, 其他点能否通过 集合中的  t  点 缩短,j点到集合的距离
    }
    return ret; 
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int u,v,w;
    cin >> n >> m;
    for(int i = 0;i <= n; ++i) 
        for(int j = 0;j <= n; ++j)
            g[i][j] = g[j][i] = INF;
    while(m--){
        cin >> u >> v >> w;
        g[u][v] = g[v][u] = min(g[u][v],w);
    }
    int ans = prim();
    cout << ans ;
    return 0;
}

Kruskal O(mlogm)

  • 稀疏图

算法流程(以边来扩展,并查集的思想)

  1. 将所有边,按权重,从小到大排序 (sort)
  2. 枚举每条边 a -> b ,权重 c
    1. 如果 a 和 b 不连通,将a -> b 这条边 加入集合中
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int M = 2e5 + 10,N = 1e5 + 10;
int p[N],n,m;
struct Edge{
    int a,b,w;
    bool operator < (const Edge & W) const{
        return w < W.w;
    }
}edges[M];
int find(int x){
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int cnt = 0,ret = 0;
    cin >> n >> m;
    for(int i = 0;i < m; ++i){
        cin >> edges[i].a >> edges[i].b >> edges[i].w;
    }
    sort(edges,edges + m);
    for(int i = 1;i <= n; ++i) p[i] = i;
    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;
            ret += w;
            cnt ++;
        }
    }
    if(cnt < n-1) cout << "impossible";
    else cout << ret ;
    return 0;
}

二分图

染色法 (用DFS判定二分图) O(m+n)

性质:

  • 一个图是二分图 当且仅当 图中不含 奇数环

    环: 从一个点出发,又回到出发点的路径

    奇数环 : 环当中边的数量 是 奇数

  • so, 如果一个图中有奇数环,那么它一定不是二分图

    证明:环当中的第一个点属于左边,第二个点,一定属于右边,。。依次类推

    ​ 假如环是奇数,我们可以推出,出发点属于 右边,与已知矛盾。。

算法流程:

  1. for i 1 ~ n

    1. 如果 i 未染色

      dfs(i) ,把 i 所在的连通块都染色一遍

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10;
int e[N],ne[N],w[N],h[N],idx,n,m;
int color[N];
void add(int a,int b,int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = 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]){
            if(!dfs(j,3-c)) return 0;
        }
        else if(color[j] == c) return 0;
    }
    return 1;
}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    memset(h,-1,sizeof h);
    int a,b,c;
    cin >> n >> m;
    for(int i = 0;i < m; ++i){
        cin >> a >> b >> c;
        add(a,b,c);
        add(b,a,c);
    }
    bool f = 1;
    for(int i = 1;i <= n; ++i){
        if(!color[i] && !dfs(i,1)){
            f = 0;
            break;
        }
    }
    if(f) cout << "Yes";
    else cout <<"No";
    return 0;
}

匈牙利算法(求最大匹配) O(m+n) 实际运行时间远小于 O(mn)

算法流程:

  1. for i 1 ~ n 依次看每个男生(左边的点)
    1. 先把所有妹子的标记 st 清空 (把右边的标记点集情况)
    2. 如果 find(i) 返回为 真 ,res ++
  2. find(x)
    1. 枚举该男生看上的女生 j(枚举当前左边点 连向的右边点)
      1. 如果当前的妹子没有被选过
        1. 当前妹纸标记被选
        2. 如果当前妹纸没有匹配男生 或者 该妹子已经匹配的男生可以找到下家的话(这样妹纸就空出来了)
          1. 标记匹配数组 match [j] = i;
    2. 最后没找到的话,就返回 false
#include <iostream>
#include <cstring>
using namespace std;
const int N = 510,M = 1e5 + 10;
int n1,n2,m;
int h[N],e[M],ne[M],idx,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] = 1;
            if(match[j] == 0 || find(match[j])){
                match[j] = x;
                return 1;
            }
        }
    }
    return 0;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int u,v,res = 0;
    cin >> n1 >> n2 >> m;
    memset(h,-1,sizeof h);
    while(m--){
        cin >> u >> v;
        add(u,v);
    }
    for(int i = 1;i <= n1; ++i){
        memset(st,0,sizeof st);
        if(find(i)) res ++;
    }
    cout << res;
    return 0;
}

原文地址:https://www.cnblogs.com/317zhang/p/11562264.html