zoukankan      html  css  js  c++  java
  • ACM常用模板图论

    常用图论算法总结,大部分代码摘自网络,个人总结整理,不定期更新

    图的存储:

    1.vector:方便易用

    #include <vector>
    #include <iostream>
    using namespace std;
    int const MAX_M=1000;
    struct Edge
    {
        int to,cost,next;
    };
    vector<Edge>G[MAX_M];
    void addedge(int u,int v,int c)
    {
        G[u].push_back((Edge){v,c,(int)G[v].size()});
    }
    int main()
    {
        for(int i=1;i<=m;i++)   //初始化
            G[i].clear();
        addedge(a,b,c);         //读入边
        for(int i=0;i<G[u].size();i++)   //遍历u连向的所有边
        {
            Edge &e=G[u][i];
            cout<<e.to<<e.next<<e.cost;
        }
    
    }

    2.链式前向星:速度快,体积小

    #include <iostream>
    #include <string.h>
    using namespace std;
    int const MAX_M=100000;     //一定要开很大,否则会TLE
    int const MAX_N=100000;
    struct Edge
    {
        int to,cost,next;
    }e[MAX_M];
    int eid,p[MAX_N];
    void init()
    {
        eid=0;
        memset(p,-1,sizeof(p));
    }
    void insert(int u,int v,int c)
    {
        e[eid].to=v;
        e[eid].cost=c;
        e[eid].next=p[u];
        p[u]=eid++;
    }
    void addedge(int u,int v,int c)
    {
        insert(u,v,c);
        insert(v,u,0);
    }
    int main()
    {
        init()    //初始化,每次必须要有!
        addedge(a,b,c);         //读入边
        for(int i=p[u];i!=-1;i=e[i].next)   //遍历u连向的所有边
        {
            cout<<u<<"->"<<e[i].to<<e[i].next<<e[i].cost;
        }
    }

    LCA最近公共祖先:

    在线算法-倍增法:O(VlogV+QlogV) Q为查询次数

    #include <iostream>
    #include <string.h>
    using namespace std;
    const int MAX_N=100000;
    const int MAX_M=10000000;
    int deep[MAX_N],f[MAX_N],anc[MAX_N][25];  //anc[v][h]表示v结点的h倍祖先的编号
    int p[MAX_N],eid;
    int n,m;
    struct node
    {
        int to,next;
    }e[MAX_N];
    void init()
    {
        memset(anc,0,sizeof(anc));
        memset(p,-1,sizeof(p));
        eid=0;
    }
    void insert(int u,int v)
    {
        e[eid].to=v;
        e[eid].next=p[u];
        p[u]=eid++;
    }
    void dfs(int x,int fa)        //构建dfs序
    {
        anc[x][0]=f[x];
        for(int i=1;i<=20;i++)
            anc[x][i]=anc[anc[x][i-1]][i-1];
        for(int i=p[x];i!=-1;i=e[i].next)
            if(e[i].to!=fa)
            {
                f[e[i].to]=x;
                deep[e[i].to]=deep[x]+1;
                dfs(e[i].to,x);
            }
    }
    int lca(int x,int y)        //求x与y的lca
    {
        if(deep[x]<deep[y])
            swap(x,y);
        for(int i=20;i>=0;i--)
            if(deep[anc[x][i]]>=deep[y])
                x=anc[x][i];
        if(x==y)  return x;
        for(int i=20;i>=0;i--)
            if(anc[x][i]!=anc[y][i])
                x=anc[x][i],y=anc[y][i];
        return f[x];
    }
    int main()
    {
        init();
        cin>>n>>m;        //顶点数,边数
        for(int i=1;i<=m;i++)
        {
            int u,v;
            cin>>u>>v;
            insert(u,v);
            insert(v,u);
        }
        deep[1]=1;
        dfs(1,0);
        int x,y,q;
        cin>>q;           //查询次数
        for(int i=1;i<=q;i++)
        {
            cin>>x>>y;
            cout<<lca(x,y)<<endl;
        }
       return 0;
    }

    树上任意两点距离之和:O(n)

    #include <iostream>
    #include <vector>
    #include <cstring>
    using namespace std;
    typedef long long ll;
    const int maxn = 100005;
    const ll mod=1e9+7;
    int sum[maxn], n;
    ll dp[maxn];
    struct Edge
    {
        int v, w;
    };
    vector<Edge> tree[maxn];
    void dfs(int cur, int father)
    {
        sum[cur] = 1;
        for(int i = 0; i < tree[cur].size(); i++)
        {
            int son = tree[cur][i].v;
            ll len = tree[cur][i].w;
            if(father == son)
                continue;
            dfs(son, cur);
            sum[cur] += sum[son];
            sum[cur]%=mod;
            dp[cur] =(dp[cur]+dp[son] + (((n-sum[son])*sum[son])%mod * len)%mod)%mod;
        }
    }
    int main()
    {
        std::ios::sync_with_stdio(false);
        calJc();
        while(cin>>n)
        {
            for(int i = 0; i <= n; i++)
                tree[i].clear();
            memset(sum, 0, sizeof(sum));
            memset(dp, 0, sizeof(dp));
            for(int i = 0; i < n-1; i++)
            {
                ll u,v,w;
                cin>>u>>v>>w;
                Edge t1, t2;
                t1.v = v;
                t1.w = w;
                t2.v = u;
                t2.w = w;
                tree[u].push_back(t1);
                tree[v].push_back(t2);
            }
            dfs(1,0);
            cout<<dp[1]<<endl;
        }
        return 0;
    }

    拓扑排序:

    #include <iostream>
    #include <string.h>
    #include <queue>
    using namespace std;
    const int MAX_N=10000;
    struct edge
    {
        int to,next;
    }e[MAX_N];
    int p[MAX_N],eid;
    void init()
    {
        eid=0;
        memset(p,-1,sizeof(p));
    }
    void insert(int u,int v)
    {
        e[eid].to=v;
        e[eid].next=p[u];
        p[u]=eid++;
    }
    int indegree[MAX_N];
    int n;
    int topo()
    {
        queue<int>Q;
        for(int i=1;i<=n;i++)
        {
            if(indegree[i]==0)
                Q.push(i);
        }
        while(!Q.empty())
        {
            int now=Q.front();
            cout<<"visting"<<now<<endl;
            Q.pop();
            for(int i=p[now];i!=-1;i=e[i].next)
            {
                int v=e[i].to;
                indegree[v]--;
                if(indegree[v]==0)
                    Q.push(v);
            }
        }
    }

    欧拉图:

    void dfs(int now)
    {
         int k;
         for(k=p[now];k!=-1;k=e[k].next)
         {
             if(!vst[k])
             {
                 vst[k]=true;
                 vst[k^1]=true;
                 dfs(e[k].to);
                 ans[ansi++]=k;
             }
         }
    }

    dfs结束后,ans中存储的就是欧拉图,可通过vst判断图的联通性,每个点都被更新则全联通
    强连通分量:


    Tarjan算法:O(V+E)

    #include <iostream>
    #include <string.h>
    using namespace std;
    const int MAX_N=10000;
    struct edge
    {
        int to,next;
    }e[MAX_N];
    int p[MAX_N],eid;
    void init()
    {
        eid=0;
        memset(p,-1,sizeof(p));
    }
    void insert(int u,int v)
    {
        e[eid].to=v;
        e[eid].next=p[u];
        p[u]=eid++;
    }
    int dfn[MAX_N],low[MAX_N];     //当前时间戳,最早次序号,dfn只能初始化为0!
    int idx=0;                                   //时间戳初始化为0
    int belong[MAX_N],scc=0;         //belong记录每个顶点属于哪个强连通分量,scc为强连通分量总数
    int s[MAX_N],top=0;                 //模拟栈
    bool instack[MAX_N];              //是否在栈中
    void tarjan(int u)
    {
        dfn[u]=low[u]=++idx;
        s[top++]=u;
        instack[u]=true;
        for(int i=p[u];i!=-1;i=e[i].next)
        {
            int v=e[i].to;
            if(!dfn[v])
            {
                tarjan(v);
                low[u]=min(low[u],low[v]);
            }
            else if(instack[v])
            {
                low[u]=min(low[u],dfn[v]);
            }
        }
        if(dfn[u]==low[u])
        {
            ++scc;
            do
            {
                belong[s[--top]]=scc;
                instack[s[top]]=false;
            }
            while(s[top]!=u);
        }
    }
    int main()
    {
        init();
        int n,m;
        cin>>n>>m;
        for(int i=0;i<m;i++)
        {
            int a,b;
            cin>>a>>b;
            insert(a,b);
        }
     //   tarjan(0);
        for(int i=1;i<=n;i++)          //用这种方法更新全部点
            if(dfn[i]==0)
                 tarjan(i);                    
        for(int i=1;i<=n;i++)
            cout<<i<<"->"<<belong[i]<<endl;
        return 0;
    }

    最短路:

    1.Bellman-Ford:

    可处理负权边,O(V*E)

         struct edge{int from,to,cost};
         edge es[N];  //边
         int d[N],V,S;    //最短距离,顶点数,边数
    
         void bellman_ford(int s)
         {
            memset(d,INF,sizeof(d));
            d[s]=0;
            while(true)
            {
                bool update = false;
                 for(int i=0;i<E;i++)
                 {
                     edge e = es[i];
                      if(d[e.from] != INF && d[e.to]>d[e.from]+e.cost)
                      {
                          d[e.to]=d[e.from]+e.cost;
                           update = true;
                      }
                 }
                 if(!update) break;
            }
         }

    检测负环:

    基于Bellman-ford

       bool find_negative_loop()   //返回true表示存在负圈
    {
        memset(d,0,sizeof(d));
        for(int i=0;i<V;i++)
        {
              for(int j=0;j<E;j++)
            {
                edge e=es[j];
                if(d[e.to]>d[e.from]+e.cost)
                    d[e.to]=d[e.from]+e.cost;
                if(i==V-1)           //如果第n次仍然更新了,则存在负圈
                    return truel
            }
        }
        return false;
    }

    2.朴素Dijkstra:

    不能处理负权边,O(E*logV)

    #include <bits/stdc++.h>
    #include <vector>
    #include <queue>
    using namespace std;
    
    const int MAX_N=1000;
    const int MAX_V=1000;
    const int INF=0x3f3f3f3f;
    struct edge
    {
        int to,cost;
    };
    typedef pair<int,int>P;    //first是最短距离,second是顶点编号
    int V;
    vector<edge>G[MAX_N];
    int d[MAX_V];
    
    void Dijstra(int s)
    {
        //通过指定greater<P>参数,堆按照first从小到大顺序取出值
        priority_queue< P,vector<P>,greater<P> > que;
        fill(d,d+V,INF);
        d[s]=0;
        que.push(P(0,s));
        while(!que.empty())
        {
            P now=que.top();
            que.pop();
            int v=now.second;
            if(d[v]<now.first) continue;
            for(int i=0;i<G[v].size();i++)
            {
                edge e=G[v][i];
                if(d[e.to]>d[v]+e.cost)
                {
                    d[e.to]=d[v]+e.cost;
                    que.push(P(d[e.to],e.to));
                }
            }
        }
    }

    Dijkstra—堆优化:

    O((V+E)logV) 推荐使用!

    const int MAX_N = 10000;
    const int MAX_M = 100000;
    const int inf = 0x3f3f3f3f;
    struct edge {
        int v, w, next;
    } e[MAX_M];
    int p[MAX_N], eid;
    int n;                        //顶点数设为全局变量
    void mapinit() {
        memset(p, -1, sizeof(p));
        eid = 0;
    }
    void insert(int u, int v, int w) {  // 插入带权有向边
        e[eid].v = v;
        e[eid].w = w;
        e[eid].next = p[u];
        p[u] = eid++;
    }
    void insert2(int u, int v, int w) {  // 插入带权双向边
        insert(u, v, w);
        insert(v, u, w);
    }
    typedef pair<int, int> PII;
    set<PII, less<PII> > min_heap;  // 用 set 来伪实现一个小根堆,并具有映射二叉堆的功能。堆中 pair<int, int> 的 second 表示顶点下标,first 表示该顶点的 dist 值
    set<PII,less<PII> >:: iterator iter;
    int dist[MAX_N];  // 存储单源最短路的结果
    bool vst[MAX_N];  // 标记每个顶点是否在集合 U 中
    bool dijkstra(int s) {
        // 初始化 dist、小根堆和集合 U
        memset(vst, 0, sizeof(vst));
        memset(dist, 0x3f, sizeof(dist));
        min_heap.insert(make_pair(0, s));
        dist[s] = 0;
        for (int i = 0; i < n; ++i) {
            if (min_heap.size() == 0) {  // 如果小根堆中没有可用顶点,说明有顶点无法从源点到达,算法结束
                return false;
            }
            // 获取堆顶元素,并将堆顶元素从堆中删除
            iter = min_heap.begin();
            int v = iter->second;
            min_heap.erase(*iter);
            vst[v] = true;
            // 进行和普通 dijkstra 算法类似的松弛操作
            for (int j = p[v]; j != -1; j = e[j].next) {
                int x = e[j].v;
                if (!vst[x] && dist[v] + e[j].w < dist[x]) {
                    // 先将对应的 pair 从堆中删除,再将更新后的 pair 插入堆
                    min_heap.erase(make_pair(dist[x], x));
                    dist[x] = dist[v] + e[j].w;
                    min_heap.insert(make_pair(dist[x], x));
                }
            }
        }
        return true;  // 存储单源最短路的结果
    }

    路径还原:

    基于Dijkstra,使用邻接矩阵

    int prev[MAX_V];
    int d[MAX_V];
    int V;
    int cost[MAX_V][MAX_V];
    bool used[MAX_V]
    void Dijkstra(int s)
    {
        fill(d,d+v,INF);
        fill(used,used+V,false);
        fill(prev,prev+V,-1);
        d[s]=0;
    
        while(true)
        {
            int v=-1;
            for(int u=0;u<V;u++)
                if(!used[u]&& (v==-1 || d[u]<d[v]) )
                v=u;
            if(v==-1) break;
            used[v]=true;
            for(int u=0;u<V;u++)
            {
                if(d[u]>d[v]+cost[v][u])
                    d[u]=d[v]+cost[v][u];
                    prev[u]=v;
            }
        }
    }
    vector <int>get_path(int t)
    {
        vector<int>path;
        for(;t!=-1;t=prev[t])
            path.push_back(t);
        reverse(path.begin(),path.end());
        return path;
    }

    3.floyd:

    计算任意两点间最短路,O(v^3)

    const int inf = 0x3f3f3f3f;
    int g[MAX_N][MAX_N];  // 算法中的 G 矩阵
    // 初始化 g 矩阵
    void init() {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i == j) {
                    g[i][j] = 0;
                } else {
                    g[i][j] = inf;
                }
            }
        }    
    }
    // 插入一条带权有向边
    void insert(int u, int v, int w) {
        g[u][v] = w;
    }
    // 核心代码
    void floyd() {
        for (int k = 0; k < n; ++k) {
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < n; ++j) {
                    if (g[i][k] + g[k][j] < g[i][j]) {
                        g[i][j] = g[i][k] + g[k][j];
                    }
                }
            }
        }    
    }

    4.SPFA:

    可处理负权,但不能处理负环,能判断负环,稀疏图O(E),稠密图O(VE)

    bool inq[MAX_N];
    int cnt[MAX_N];  //记录每个顶点入队次数,若某点入队超过n次,则存在负环
    int d[MAX_N];  // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
    void spfa(int s) {
        memset(inq, 0, sizeof(inq));
        memset(d, 0x3f, sizeof(d));
        d[s] = 0;
        inq[s] = true;
        queue<int> q;
        q.push(s);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            if(cnt[u]>n)
                cout<<存在负环;
            inq[u] = false;
            for (int i = p[u]; i != -1; i = e[i].next) {
                int v = e[i].v;
                if (d[u] + e[i].w < d[v]) {
                    d[v] = d[u] + e[i].w;
                    if (!inq[v]) {
                        q.push(v);
                        cnt[v]++;
                        inq[v] = true;
                    }
                }
            }
        }
    }

    K短路:spfa+A*

    #include <iostream>
    #include <stdio.h>
    #include <cstring>
    #include <queue>
    using namespace std;
    
    int const maxn=10005;
    int const maxm=1005;
    int const INF=0x3f3f3f3f;
    
    int n,m,S,E,T,k;
    int p1[maxn],p2[maxn],eid1,eid2;
    bool vis[maxm];
    int dis[maxm];
    struct node
    {
        int to,next,cost;
    }e1[maxn],e2[maxn];
    
    struct node2
    {
        int to,g,f;
        bool operator<(const node2 &r ) const
        {
            if(r.f==f)
                return r.g<g;
            return r.f<f;
        }
    };
    void init()
    {
        eid1=1;
        eid2=1;
        memset(p1,-1,sizeof(p1));
        memset(p2,-1,sizeof(p2));
    }
    
    void insert1(int u,int v,int w)       //正向存图
    {
        e1[eid1].to=v;
        e1[eid1].cost=w;
        e1[eid1].next=p1[u];
        p1[u]=eid1++;
    }
    void insert2(int u,int v,int w)       //反向存图
    {
        e2[eid2].to=v;
        e2[eid2].cost=w;
        e2[eid2].next=p2[u];
        p2[u]=eid2++;
    }
    
    
    void spfa(int s)               //处理出从终点到所有点的最短路
    {
        queue<int>Q;
        memset(vis,0,sizeof(vis));
        memset(dis,INF,sizeof(dis));
        dis[s]=0;
        vis[s]=1;
        Q.push(s);
        while(!Q.empty())
        {
            int u=Q.front();
            vis[u]=0;
            Q.pop();
            for(int i=p2[u];i!=-1;i=e2[i].next)
            {
                int v=e2[i].to;
                if(dis[v]>dis[u]+e2[i].cost)
                {
                    dis[v]=dis[u]+e2[i].cost;
                    if(!vis[v])
                    {
                        vis[v]=1;
                        Q.push(v);
                    }
                }
            }
    
        }
    }
    
    int Astar()                 //A*求k短路
    {
        node2 e,now;
        int cnt=0;
        priority_queue<node2>Q;
        if(S==E)
            k++;
        if(dis[S]==INF)
            return -1;
        e.to=S;
        e.g=0;
        e.f=e.g+dis[e.to];
        Q.push(e);
        while(!Q.empty())
        {
            e=Q.top();
            Q.pop();
            if(e.to==E)
                cnt++;
            if(cnt==k)
                return e.g;
            int u=e.to;
            for(int i=p1[u];i!=-1;i=e1[i].next)
            {
                now.to=e1[i].to;
                now.g=e.g+e1[i].cost;
                now.f=now.g+dis[now.to];
                Q.push(now);
                if(now.g>T) return -1;
            }
        }
        return -1;
    }
    int main()
    {
        //std::ios::sync_with_stdio(false);
        while(~scanf("%d%d",&n,&m))
        {
            init();
            scanf("%d%d%d%d",&S,&E,&k,&T);      //起点,终点,k短路,限定条件
            for(int i=1;i<=m;i++)
            {
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                insert1(u,v,w);
                insert2(v,u,w);
            }
            spfa(E);
            Astar();
            int ans=Astar();
            printf("%d",ans);   //输出k短路长度
        }
    }
    

    差分约束系统:

    即利用最短路算法解不等式,形如a-b<=k,则建立有向边a->b,权值为k
    若a=b,即相当a<=b && a>=b ,即建立a->b 和 b->a 两条权值为0的边
    加入超级源点,到达所有顶点,且权值为0
    利用spfa算法,判断负环,若不存在,则该系统有解,若存在,则该系统无解


    最小生成树:

    1.Prim:O(n^2)

        int cost[MAX_N][MAX_N]; //cost[u][v] 表示边e=(u,v)的权值,不存在为INF
         int mincost[MAX_N];  //从集合T出发的边到每个顶点的最小权值
         bool used[MAX_N];    //顶点i是否包含在集合T中
         int n;           //顶点数
    
         int prim()
         {
             for(int i=0;i<n;++i)         //初始化
              {
                  mincost[i]=INF;
                   used[i]=false;
              }
              mincost[0]=0;
              int res=0;
    
              while(true)
              {
                   int v=-1;    //从不属于T的顶点中选取T到其权值最小的顶点
                   for(int u=0;u<n;u++)
                       if(!used[u] && (v==-1 || mincost[u]<mincost[v]))
                            v=u;
                   if(v==-1) break;  //没有可达点
                   used[v]=true;   //把顶点v加入x
                   res += mincost[v];  //更新结果
                   for(int u=0;u<V;u++)
                       mincost[u]=min(mincost[u],cost[v][u]);
              }
              return res;    //返回最小生成树总权值
         }

    2.Kruskal:O(eloge)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int MAX_N = 100000;  // 最大顶点数
    const int MAX_M = 100000;  // 最大边数
    struct edge {
        int u, v, w;
    }e[MAX_M];
    int fa[MAX_N], n, m;  // fa 数组记录了并查集中结点的父亲
    bool cmp(edge a,edge b) {
        return a.w < b.w;
    }
    // 并查集相关代码
    int ancestor(int x) {  // 在并查集森林中找到 x 的祖先,也是所在连通块的标识
        if(fa[x] == x) return fa[x];
        else return fa[x] = ancestor(fa[x]);
    }
    int same(int x, int y) {  // 判断两个点是否在一个连通块(集合)内
        return ancestor(x) == ancestor(y);
    }
    void merge(int x, int y) {  // 合并两个连通块(集合)
        int fax = ancestor(x), fay = ancestor(y);
        fa[fax] = fay;
    }
    int Kruskal()
    {
        for (int i = 1; i <= n; i++) {
            fa[i] = i;
        }
        int rst = n, ans = 0;  // rst 表示还剩多少个集合,ans 保存最小生成树上的总边权
        for (int i = 1; i <= m && rst > 1; i++) {
            int x = e[i].u, y = e[i].v;
            if (same(x, y)) {
                continue;  // same 函数是查询两个点是否在同一集合中
            } else {
                merge(x, y);  // merge 函数用来将两个点合并到同一集合中
                rst--;  // 每次将两个不同集合中的点合并,都将使 rst 值减 1
                ans += e[i].w;  // 这条边是最小生成树中的边,将答案加上边权
            }
        }
        return ans;
    }
    int main() {
        scanf("%d%d", &n, &m);  // n 为点数,m 为边数
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);  // 用边集数组存放边,方便排序和调用
            }
        sort(e + 1, e + m + 1, cmp);  // 对边按边权进行升序排序
        cout<<Kruskal()<<endl;
        return 0;
    }

    二分图:

    二分图判断:

    bool check()
    {
        memset(used,-1,sizeof(used));
        queue<int>Q;
        Q.push(1);
        used[1]=0;
        while(!Q.empty())
        {
            int now=Q.front();
            for(int i=1;i<=n;i++)    //遍历所有点
            {
                if(map[now][i]==0)    //邻接矩阵存图
                    continue;
                int v=i;
                if(used[v]==-1)
                {
                    used[v]=(used[now]+1)%2;
                    Q.push(v);
                }
                else
                {
                    if(used[v]==used[now])
                        return false;
                }
            }
            Q.pop();
        }
        return true;
    }

    匈牙利算法:

    const int MAX_N = 100;  // X 集合中的顶点数上限
    const int MAX_M = 10000;  // 总的边数上限
    struct edge {
        int v, next;
    } e[MAX_M];
    int p[MAX_N], eid;
    void init() {
        memset(p, -1, sizeof(p));
        eid = 0;
    }
    void insert(int u, int v) {  // 从 X 集合顶点 u 到 Y 集合顶点 v 连一条边,注意 u 和 v 的编号无关
        e[eid].v = v;
        e[eid].next = p[u];
        p[u] = eid++;
    }
    bool vst[MAX_N];  // 标记一次 dfs 过程中,Y 集合中的顶点是否已访问
    int ans[MAX_N];  // 标记 Y 集合中的顶点匹配的 X 集合中的顶点编号
    int n, m;  // n 表示 X 集合中的顶点数,假设顶点编号为 0..n-1
    bool dfs(int u) {
        for (int i = p[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if (!vst[v]) {  // 如果 Y 集合中的 v 还没有被访问
                vst[v] = true;
                if (ans[v] == -1 || dfs(ans[v])) {  // 如果 v 没有匹配点,或 v 的匹配点能找到一条到一个未匹配点的增广路,则将 v 的匹配点设为 u
                    ans[v] = u;
                    return true;
                }
            }
        }
        return false;  // 没找到增广路
    }
    int maxmatch() {
        int cnt = 0;
        memset(ans, -1, sizeof(ans));  // 初始将所有 Y 集合中顶点的匹配编号设为 -1
        for (int i = 0; i < n; ++i) {
            memset(vst, 0, sizeof(vst));  // 进行 dfs 前,将 vst 清空
            cnt += dfs(i);  // 如果找到增广路,则将 cnt 累加 1
        }
        return cnt;  // cnt 是找到增广路的次数,也是总的最大匹配数
    }

    网络流

    1.最大流—Dinic算法:

    #include <iostream>
    #include <queue>
    #include <string.h>
    using namespace std;
    
    const int MAX_N=1000;
    const int MAX_M=100000;
    const int INF=0x3f3f3f3f;
    
    struct edge
    {
        int v,c,next;
    }e[MAX_M];
    
    int p[MAX_N],eid;
    void init()
    {
        memset(p,-1,sizeof(p));
        eid=0;
    }
    void insert(int u,int v,int c)
    {
        e[eid].v=v;
        e[eid].next=p[u];
        e[eid].c=c;
        p[u]=eid++;
    }
    void addedge(int u,int v,int c)
    {
        insert(u,v,c);
        insert(v,u,0);
    }
    int S,T;                 //源点和汇点
    int d[MAX_N];       //d表示当前点的层数(level)
    bool bfs()
    {
        memset(d,-1,sizeof(d));
        queue<int>q;
        q.push(S);
        d[S]=0;
        while(!q.empty())
        {
            int u=q.front();
            q.pop();
            for(int i=p[u];i!=-1;i=e[i].next)
            {
                int v=e[i].v;
                if(e[i].c>0 && d[v]==-1)
                {
                    q.push(v);
                    d[v]=d[u]+1;
                }
            }
        }
        return (d[T]!=-1);
    }
    
    int dfs(int u,int f)
    {
        if(u==T)
            return f;
        int res=0;
        for(int i=p[u];i!=-1;i=e[i].next)
        {
            int v=e[i].v;
            if(e[i].c>0 && d[u]+1 == d[v])
            {
                int tmp=dfs(v,min(f,e[i].c));      //递归计算顶点v,用c(u,v)更新当前流量上限
                f-=tmp;
                e[i].c-=tmp;
                res+=tmp;
                e[i^1].c+=tmp;                          //修改反向弧流量
                if(f==0)                                      //流量达到上限,不必继续搜索
                    break;
            }
        }
        if(res==0)                    //当前没有经过顶点u的流,不必再搜索顶点u
            d[u]=-1;
        return res;
    }
    
    int maxf()                 //计算最大流
    {
        int res=0;
        while(bfs())
        {
            res+=dfs(S,INF);           //初始流量上限为INF
        }
        return res;
    }
    
    int main()
    {
        int t,cnt=1;
        cin>>t;
        while(t--)
        {
            init();
            int m,n;
            cin>>n>>m;
            for(int i=0;i<m;i++)
            {
                int a,b,c;
                cin>>a>>b>>c;
                addedge(a,b,c);
            }
         /*   for(int k=1;k<=n;k++)                       //输出图,用于检测图的读入是否正确
            for(int i=p[k];i!=-1;i=e[i].next)
            {
                cout<<k<<"->"<<e[i].v<<" "<<e[i].c<<endl;
            }*/
    
            S=1;
            T=n;
            cout<<"Case "<< cnt++ <<": ";
            cout<<maxf()<<endl;
        }
        return 0;
    }

    2.最小费用流–spfa费用流:

    #include <iostream>
    #include <string.h>
    #include <queue>
    using namespace std;
    
    const int MAX_N = 10005;
    const int MAX_M = 100005;
    const int inf = 0x3f3f3f3f;
    
    struct edge
    {
        int v, c, w, next;  // v 表示边的另一个顶点,c 表示当前剩余容量,w 表示单位流量费用
    } e[MAX_M];
    
    int p[MAX_N], s, t, eid;  // s 表示源点,t 表示汇点,需要在进行 costflow 之前设置完毕
    
    void init()
    {
        memset(p, -1, sizeof(p));
        eid = 0;
    }
    
    void insert(int u, int v, int c, int w) {
        e[eid].v = v;
        e[eid].c = c;
        e[eid].w = w;
        e[eid].next = p[u];
        p[u] = eid++;
    }
    void addedge(int u, int v, int c, int w) {
        insert(u, v, c, w);
        insert(v, u, 0, -w);
    }
    
    bool inq[MAX_N];
    int d[MAX_N];  // 如果到顶点 i 的距离是 0x3f3f3f3f,则说明不存在源点到 i 的最短路
    int pre[MAX_N];  // 最短路中连向当前顶点的边的编号
    
    bool spfa()
    {  // 以源点 s 为起点计算单源最短路,如果不存在从 s 到 t 的路径则返回 false,否则返回 true
        memset(inq, 0, sizeof(inq));
        memset(d, 0x3f, sizeof(d));
        memset(pre, -1, sizeof(pre));
        d[s] = 0;
        inq[s] = true;
        queue<int> q;
        q.push(s);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            inq[u] = false;
            for (int i = p[u]; i != -1; i = e[i].next) {
                if (e[i].c) {
                    int v = e[i].v;
                    if (d[u] + e[i].w < d[v]) {
                        d[v] = d[u] + e[i].w;
                        pre[v] = i;
                        if (!inq[v]) {
                            q.push(v);
                            inq[v] = true;
                        }
                    }
                }
            }
        }
        return pre[t] != -1;
    }
    
    int costflow() {  // 计算最小费用最大流
        int ret = 0;  // 累加和
        while(spfa()) {
            int flow = inf;
            for(int i = t; i != s; i = e[pre[i]^1].v) {
                flow = min(e[pre[i]].c, flow);  // 计算当前增广路上的最小流量
            }
            for(int i = t; i != s; i = e[pre[i]^1].v) {
                e[pre[i]].c -= flow;
                e[pre[i]^1].c += flow;
                ret += e[pre[i]].w * flow;
            }
        }
        return ret;
    }
    
    int main()         //以最短来回路为例
    {
        int n,m;
        cin>>n>>m;
        init();
        s=0;
        t=n+1;
        addedge(s,1,2,0);
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            addedge(a,b,1,c);
            addedge(b,a,1,c);
        }
        addedge(n,t,2,0);
        int ans=costflow();
        cout<<ans<<endl;
        return 0;
    }

  • 相关阅读:
    面试题:给定一个长度为N的数组,其中每个元素的取值范围都是1到N。判断数组中是否有重复的数字
    位运算技巧3
    Android消息循环分析
    ubuntu安装软件的方式
    fragment Trying to instantiate a class com.example.testhuanxindemo.MyFragment that is not a Fragmen
    LAN路由
    php 简易验证码(GD库)
    飘逸的python
    它们,不能是虚函数!!!
    HTML5调用摄像头实现拍照功能(兼容各大主流浏览器)
  • 原文地址:https://www.cnblogs.com/floatingcloak/p/10344175.html
Copyright © 2011-2022 走看看