zoukankan      html  css  js  c++  java
  • 最小生成树 二分图 模板

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

    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;
    }
    
  • 相关阅读:
    springboot使用mybatis-plus表单更新null值问题通用解决方案
    ASP.NET MVC快速开发框架FastExecutor开发全过程感受及总结
    NetCore实现Transitional自定义注解事物
    NetCore3.0实现自定义IOC容器注入
    ADO.NET事务封装
    ASP.NET MVC模块化开发——动态挂载外部项目
    后台管理tab栏滑动解决方案
    c#使用CefSharp开发winform——环境搭建
    c#通过Redis实现轻量级消息组件
    ASP.NET MVC实现依赖注入
  • 原文地址:https://www.cnblogs.com/lukelmouse/p/11562264.html
Copyright © 2011-2022 走看看