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;
}

原文地址:https://www.cnblogs.com/floatingcloak/p/10344175.html