Graph Theory Template

拓扑排序

  queue<int> Q;
  for(int i = 1; i <= n; i++)
    if(d[i] == 0) {Q.push(i);cmp[i] = 1;}
  while(!Q.empty())
  {
    int v = Q.front();
    Q.pop();
    for(int i = 0; i < G[v].size(); i++)
      {
        d[G[v][i]]--;
        if(!d[G[v][i]])
        {
          cmp[G[v][i]] = cmp[v]+1;
          Q.push(G[v][i]);
        }
      }
  }

2-sat & 强联通分量分解

kosaraju

两遍 dfs kosaraju 比tarjan慢

//vector存边比较慢,可以考虑用链式前向星
int n,m;
vector <int> G[maxn];//正向邻接表
vector <int> rG[maxn];//反向邻接表
vector <int> vs;     //拓扑序存点
int cmp[maxn]; //标号
bool used[maxn];

void dfs(int x)
{
  used[x] = true;
  for(int i = 0; i < G[x].size(); i++)
   if(!used[G[x][i]])  dfs(G[x][i]);
  vs.push_back(x);
}

void rdfs(int x, int k)
{
  used[x] = true;
  cmp[x] = k;
  for(int i = 0; i < rG[x].size(); i++)
    if(!used[rG[x][i]]) rdfs(rG[x][i],k);
}

int scc()
{
  memset(used, false , sizeof(used));
  vs.clear();
  for(int i = 1; i <= n; i++)
     if(!used[i]) dfs(i);
  memset(used, false ,sizeof(used));
  int k = 0;
  for(int i = vs.size()-1; i >= 0; i--)
     if(!used[vs[i]])  rdfs(vs[i],++k);
  return k;
}

Tarjan

int n,m,tot,top,cnt,cnts;
// top 栈顶 cnts 强连通分量数量 cnt 时间戳
int sta[maxn],h[maxn],dfn[maxn],low[maxn],cmp[maxn];
//sta[] 堆栈, h[] 链式前向星  dfn[]dfs次序数组 cmp[]强连通分量标号
//low[] u结点或者u的子树结点所能追溯到的最早栈中结点的次序号
bool ins[maxn];
//判断是否在栈中

struct edge{
  int to,next;
  edge(int x = 0, int y = 0) : to(x), next(y) {}
}es[maxm];

void add_edge(int u,int v)
{
  es[tot] = edge(v,h[u]);
  h[u] = tot++;
}

init()
{
  tot = top = cnts = cnt = 0;
  memset(ins,false,sizeof(ins));
  memset(h,-1,sizeof(h));
  memset(dfn,0,sizeof(dfn));
}
void Tarjan(int x)
{
  dfn[x] = low[x] = ++cnt;
  ins[x] = true;
  sta[top++] = x;
  for(int i = h[x];  ~i ; i = es[i].next)
  {
    int v = es[i].to;
    if(!dfn[v])
    {
      Tarjan(v);
      if(low[x] > low[v])  low[x] = low[v];
    }
    else if(ins[v] && dfn[v] < low[x])
     low[x] = dfn[v];
  }
  if(dfn[x] == low[x])
  {
    cnts++;
    int t;
    do{
      t = sta[--top];
      ins[t] = false;
      cmp[t] = cnts;
    }while(t != x);
  }
}

void scc()
{
  for(int i = 1; i <= n; i++)
    if(!dfn[i]) Tarjan(i);
}

有序输出

#include <bits/stdc++.h>

using namespace std;
const int maxn = 8000 * 2 + 100;
vector <int> G[maxn];
bool used[maxn];
int n,m,top;
int sta[maxn];

bool dfs(int x)
{
    if(used[x^1]) return false;
    if(used[x])   return true;
    used[x] = true;
    sta[top++] = x;
    for(int i = 0; i < G[x].size(); i++)
      if(!dfs(G[x][i])) return false;
    return true;
}

bool judge()
{
    memset(used,false,sizeof(used));
    for(int i = 0; i < 2*n; i++)
    {
        if(!used[i] && !used[i^1])
        {
            top = 0;
            if(!dfs(i))
            {
                for(int j = 0; j < top; j++)
                   used[sta[j]] = false;
                top = 0;
                if(!dfs(i^1)) return false;
            }
        }
    }
    return true;
}

void solve()
{
  if(judge())
    {
      for(int i = 0; i < 2*n; i++)
      if(used[i]) printf("%d\n",i+1);
    }
  else puts("NIE");
}

LCA

倍增

int fa[maxn][32];
int depth[maxn];
//depth[root] = 1, dfs(root)
void dfs(int x)
{

    for(int i = 1; i <= 18; i++)
    {
        if(depth[x] < (1<<(i-1))) break;
        fa[x][i] = fa[fa[x][i-1]][i-1];
    }
    for(int i = h[x]; ~i; i = es[i].next)
    {
      int v = es[i].to;
      if(depth[v] >= 0) continue;
       fa[v][0] = x;
       depth[v] = depth[x] + 1;
       dfs(v);
    }
}

int lca(int x, int y)
{
    if(depth[x] < depth[y]) swap(x,y);
    int d = depth[x] - depth[y];
    for(int i = 0; i <= 18; i++)
        if(d & (1<<i)) x = fa[x][i];
    if(x == y) return x;
    for(int i = 18; i >= 0; i--)
        if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}

求树的直径

  • 两遍dfs
//Codeforces 592 D. Super M

#include <bits/stdc++/h>

#define r(a) scanf("%d",&a)
#define p(a) printf("%d",a)
const int INF = 0x3f3f3f3f;
const int maxn = 123456 + 100;
using namespace std;

vector <int> g[maxn];
int a[maxn];
int b[maxn];
bool vis[maxn];
set <int> cnt;
int st;
int ed;
int len_max;

void dfs_1(int x,int num)
{
    int rt = 0;
    for(int i = 0; i < g[x].size();i++)
    {
        int t = g[x][i];
        if(cnt.count(t) == 0 || vis[t]) continue;
                vis[t] = true;
        dfs_1(t,num+1);
                rt++;
    }
    if(rt == 0 && num >= len_max)
    {
        if(len_max == num) st = min(st,x);
        else st = x;
            len_max = num;
    }
}

bool dfs(int f,int x)
{
    bool rt = false;
    for(int i = 0; i < g[x].size(); i++)
    {
        int t = g[x][i];
        if(t == f) continue;
        if( dfs(x,t)||cnt.count(t)) vis[t] = rt = true;
    }
//  if(rt) cout << "##" << x<<endl;
    return rt;
}

void get_tree()
{
  memset(vis,false,sizeof(vis));
  vis[a[0]] = true;
 // cout << "##"<<a[0]<<endl;
    dfs(0,a[0]);
}

int main()
{
    int n,m;
        r(n); r(m);
    for(int i = 1; i < n; i++)
    {
        int v,u;
        r(v); r(u);
        g[v].push_back(u);
        g[u].push_back(v);
    }
        for(int i = 0; i < m; i++)
    {  r(a[i]);  cnt.insert(a[i]);}

    get_tree();
    int len = 0;
    for(int i = 1; i <= n; i++)
           if(vis[i]) {b[++len] = i; cnt.insert(i);}
//  cout << endl;

       memset(vis,false,sizeof(vis));
       st = INF;
       vis[b[1]] = true;
       len_max = 0;
      dfs_1(b[1],1);

       memset(vis,false,sizeof(vis));
       ed = st;
       st = INF;
       vis[ed] = true;
       len_max = 0;
       dfs_1(ed,1);
       printf("%d %d\n",min(st,ed),2*(len-1)-(len_max-1));
       return 0;
}

生成树

最小生成树

  • Kruskal / 并查集 O(ElgE)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int maxm = 1e6 + 10;
const int INF = 0x3f3f3f3f;

int n,m;
int f[maxn];
struct edge{
    int u,v,c;
    edge(int u = 0, int v = 0, int c = 0) : u(u), v(v), c(c) {}
}es[maxm];

bool cmp(const edge &a,const edge &b)
{
     return a.c < b.c;
}

void init()
{
    for(int i = 0; i <= n; i++) f[i] = i;
}

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

void unit(int x,int y)
{
    x = find(x);
    y = find(y);
    f[x] = y;
}

bool same(int x,int y)
{
   return find(x) == find(y);
}

int main()
{
   scanf("%d%d",&n,&m);
   init();
   for(int i = 0; i < m; i++)
   {
     int u,v,c;
     scanf("%d%d%d",&u,&v,&c);
     es[i] = edge(u,v,c);
   }
   sort(es,es+m,cmp);
   int ans = 0,cnt = 0;
   for(int i = 0; i < m; i++)
   {
     if(!same(es[i].u,es[i].v))
     {
       unit(es[i].u,es[i].v);
       ans += es[i].c;
       if(++cnt == n-1) break;
     }
   }
   printf("%d\n",ans);
   return 0;
}

次小生成树

//O(VElogE)
int n,m;
int g[maxn][maxn];
bool in[maxn][maxn];//标记其是否是最小生成树上的边
int md[maxn][maxn];//记录最小生成树的u-v之间的最大边权
//省略并查集模板
int vis[maxn];
//构造最大边权
void dfs(int s,int x, int dis)
{
   vis[x] = true;
   for(int i = 1; i <= n; i++)
    {
       if(g[x][i] != -1 && !vis[i] && in[x][i])
            {
             md[s][i] = max(dis,g[x][i]);
             dfs(s,i,max(dis,g[x][i]));
             }
    }
}

int Unique_MST()
{
  init();
  memset(g,-1,sizeof(g));
  memset(in,false,sizeof(in));
  for(int i = 0; i < m; i++)
   {
      int u,v,c;
      scanf("%d%d%d",&u,&v,&c);
      es[i] = edge(u,v,c);
      g[u][v] = g[v][u] = c;
   }
 sort(es,es+m,cmp);
 int res = 0, cnt = 0;
 for(int i = 0; i < m; i++)
  {
     if(!same(es[i].u,es[i].v))
       {
          res += es[i].c;
          unit(es[i].u,es[i].v);
          in[es[i].u][es[i].v] = in[es[i].v][es[i].u] = true;
          if(++cnt == n-1) break;
       }
  }
 for(int i = 1; i <= n; i++)
    {
       memset(vis,false,sizeof(vis));
       dfs(i,i,0);
    }
 //cout << res << endl;
 int ans = INF;
 for(int i = 0; i < m; i++)
       {
          if(in[es[i].u][es[i].v]) continue;
          ans = min(ans,res-md[es[i].u][es[i].v] + es[i].c);
          //cout << es[i].u << " " << es[i].v << " " << md[es[i].u][es[i].v] << endl;
       }
 return ans;
}

最小树形图

  • 朱刘算法 O(VM)

不定根最小树形图就再加一个超级源点,源点与所有节点连边,边权 = sum + 1(sum = c1 + ..cm)即可。
然后最后与原点相连的就是root.主要修改下面两个地方

   if(es[i].c < in[v] && u != v)
       {
         in[v] = es[i].c,pre[v] = u;
         if(u == root) rt = i; //利用边m - m+n-1 与点1-n的映射关系
       }


    for(int i = 1; i <= n; i++)
        es[m+i-1] = edge(n+1,i,sum);
    type ans = MTG(n+1,n+1,m+n);
    if(ans >= 2*sum) printf("impossible\n\n");
    else printf("%lld %d\n\n",ans-sum,rt-m);
const double eps = 1e-8;
#define type double
type in[maxn];
int id[maxn],used[maxn],pre[maxn];

struct edge
{
  int u,v;
  type c;
  edge(int x = 0, int y = 0, type z = 0) : u(x),v(y),c(z) {}
}es[maxm];

type MTG(int root, int n, int m)
{
  type res = 0;
  while(1)
  {
    for(int i = 1; i <= n; i++)  in[i] = INF,id[i] = used[i] = -1;
    for(int i = 0; i < m; i++)
     {
       int u = es[i].u;
       int v = es[i].v;
       if(es[i].c < in[v] && u != v)  in[v] = es[i].c,pre[v] = u;
     }//找最小的入边
    for(int i = 1; i <= n; i++)
     {
       if(i == root) continue;
       res += in[i];
       if(fabs(in[i] - INF) < eps) return -1;
     }//加入所有的入边权到答案中
    int cnt = 0;
    for(int i = 1; i <= n; i++)//枚举每个点为起点进行找环
    {
      int v = i;
      while(v != root && used[v] != i && id[v] == -1)  used[v] = i,v = pre[v];
      if(v != root && id[v] == -1)//找到环的时候进行缩点编号
       {
         ++cnt;
         id[v] = cnt;
         for(int u = pre[v]; u != v; u = pre[u]) id[u] = cnt;
       }
    }
    if(cnt == 0) break;//无环则退出
    for(int i = 1; i <= n; i++)
       if(id[i] == -1)  id[i] = ++cnt;

    for(int i = 0; i < m; i++)//更新边
     {
       int u = es[i].u;
       int v = es[i].v;
       es[i].u = id[u];
       es[i].v = id[v];
       if(u != v)  es[i].c -= in[v]; //更新边权
     }
     n = cnt;//更新节点数目和根节点编号
     root = id[root];
     }
     return res;
  }

生成树计数

  • 基尔霍夫矩阵树定理

无向图的基尔霍夫矩阵: 对角线上表示每个点的度数,若ij之间有边则矩阵ij处为-1
无向图的生成树的数目为: 任意一个n-1阶主子式的行列式的绝对值.

#define type long long
int n,m;
int d[maxn];//记录度数
int g[maxn][maxn];//邻接矩阵
type c[maxn][maxn];//基尔霍夫矩阵

//行列式求和
type det(type a[][maxn], int n)
{  
  type ret=1;
  for(int i=1;i<=n;i++)
  {
    for(int j=i+1;j<=n;j++)
    {
      while(a[j][i])
      {
        type t=a[i][i]/a[j][i];
        for(int k=i;k<=n;k++)
            a[i][k]=a[i][k]-a[j][k] * t;

        for(int k=i;k<=n;k++)
            swap(a[i][k],a[j][k]);
        ret=-ret;
      }
    }
    if(a[i][i]==0)  return 0;
    ret=ret*a[i][i];
  }
  if(ret<0)   ret=-ret;
  return ret;
}

type num_st(int n, int m)
{
     for(int i = 1; i <= n; i++)
       for(int j = 1; j <= n; j++)
         {
           if(i == j) c[i][j] = d[i];
           else c[i][j] = -g[i][j];
         }

    // for(int i = 1; i <= n; i++)
    //  for(int j = 1; j <= n; j++)
    //     printf("%lld%c",c[i][j],j == n ? '\n' : ' ');

    return det(c,n-1);
}

单源最短路

dijkstra

O(ElgV) 适合不含非负权边最短路

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair <int,int> P;
const int maxn = 100+10;
const int INF = 0x3f3f3f3f;
int n,m;
int d[maxn];

struct edge
{
    int to,cost;
    edge(int u = 0,int d = 0) : to(u),cost(d) {}
};

vector <edge> G[maxn];

void dijkstra()
{
    memset(d,INF,sizeof(d));
    d[1] = 0;
    priority_queue < P,vector<P>,greater<P> >  Q;//P.first 是距离,.P.second 是顶点
    Q.push(P(0,1));
    while(!Q.empty())
    {
        P x = Q.top();
        Q.pop();
        int dist = x.first;
        int u = x.second;

        if(d[u] < dist) continue;//排除u相同时, dist不同的情况。
        for(int i = 0; i < G[u].size(); i++)
        {
            edge e = G[u][i];
            if(d[e.to] > d[u] + e.cost)
            {
                d[e.to] = d[u]+ e.cost;
                Q.push(P(d[e.to],e.to));
            }
        }
    }
    return ;
}

void input()
{
    for(int i = 0; i <= n; i++ )
        G[i].clear();
    for(int i = 0; i < m; i++)
    {
      int u,v,dist;
      scanf("%d%d%d",&u,&v,&dist);
      G[u].push_back(edge(v,dist));
      G[v].push_back(edge(u,dist));
    }
}

int main()
{
    while(scanf("%d%d",&n,&m) && n && m)
    {
        input();
        dijkstra();
        printf("%d\n",d[n]);
     }
    return 0;
}

spfa

  • bfs O(KE - VE)
bool spfa()
{
    memset(d,INF,sizeof(d));
    memset(cnt,0,sizeof(cnt));
    memset(inq,false,sizeof(inq));
    queue <int> Q;
    Q.push(1);
    d[1] = 0;
    cnt[1] = 1;
    inq[1] = true;
    while(!Q.empty())
    {
        int t = Q.front();
        Q.pop();
        inq[t] = false;
        for(int i = 0; i < G[t].size(); i++)
        {
            edge e = G[t][i];
            if(d[e.to] > d[t] + e.cost)
            {
                d[e.to] = d[t] + e.cost;
                if(!inq[e.to]) {
                    if(++cnt[e.to] > n)  return false;
                    Q.push(e.to);
                    inq[e.to] = true;
                }
            }
        }
    }
    return true;
}
  • dfs + spfa 找负环 O(KM)
//  memset(vis,false,sizeof(vis));
//  memset(d,INF,sizeof(d));
//   d[0] = 0;
int d[maxn];
bool vis[maxn];

bool spfa(int u)
{
  vis[u] = true;
  for(int i = h[u]; ~i ; i = es[i].next)
  {
    int v = es[i].to;
    if(d[v] > d[u] + es[i].c)
    {
      d[v] = d[u] + es[i].c;
      if(vis[v]) return false;
      if(!spfa(v)) return false;
    }
  }
  vis[u] = false;
  return true;
}

Bellman_Ford O(VE)

#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair <int,int> P;
const int maxn = 100+10;
const int maxm = 1e4 + 100;
const int INF = 0x3f3f3f3f;
int n,m;
int d[maxn];

struct edge {
  int from,to,cost;
};

edge es[maxm*2];

void Bellman_Ford()
{
    memset(d,INF,sizeof(d));
    d[1] = 0;
    for(int i = 1; i < n; i++)
    {
        for(int j = 1; j <= 2*m; j++)
        {
            edge e = es[j];
            if(d[e.from] < INF && d[e.to] > d[e.from] + e.cost)  d[e.to] = d[e.from] + e.cost;
        }
    }
}

int main()
{
    while(scanf("%d%d",&n,&m)&& n && m)
    {
        int j = 1;
        for(int i = 1; i <= m; i++)
        {
            int u,v,dist;
            scanf("%d%d%d",&u,&v,&dist);
            edge e1,e2;
            e1.from = u,e1.to = v,e1.cost = dist;
            e2.from = v,e2.to = u,e2.cost = dist;
            es[j++] = e1;
            es[j++] = e2;
        }
        Bellman_Ford();
        printf("%d\n",d[n]);
    }
}

多源最短路

Floyd

  • O(V^3)
#include <bits/stdc++.h>

using namespace std;
typedef long long LL;
typedef pair <int,int> P;
const int maxn = 100+10;
const int maxm = 1e4 + 100;
const int INF = 0x3f3f3f3f;
int n,m;
int mp[maxn][maxn];

void Floyd()
{
    for(int k = 1; k <= n; k++)
        for(int i = 1; i <= n; i++)
          for(int j = 1; j <= n; j++)
               mp[i][j] = min(mp[i][j],mp[i][k]+mp[k][j]);
}

int main()
{
    while(scanf("%d%d",&n,&m)&& n && m)
    {
        memset(mp,INF,sizeof(mp));
        int j = 1;
        for(int i = 1; i <= m; i++)
        {
            int u,v,dist;
            scanf("%d%d%d",&u,&v,&dist);
            mp[u][v] = dist;
            mp[v][u] = dist;
        }
        Floyd();
        printf("%d\n",mp[1][n]);
    }
    return 0;
}

网络流

最大流

EK

//Max_flow
//@2018/05/02 Wednesday
//EK algorithm [Edmonds Karp] O(V*E^2) O(v^2)
//by Tawn
#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e3;
const int INF = 0x3f3f3f3f;

int n,m; //n - Vertices  m - edges
int pre[maxn]; //record predecesor and sign if it is visited
int cap[maxn][maxn]; //record the capacity of residual network
int flow[maxn]; //record the residual flow from starting vertex to current vertex
queue <int> q;

int bfs(int st, int ed)
{
  memset(pre,-1,sizeof(pre));
  while(!q.empty()) q.pop();
  pre[st] = 0;
  flow[st] = INF;
  q.push(st);
  while(!q.empty())
  {
      int t = q.front();
      q.pop();
      if(t == ed) break;
      for(int i = 1; i <= n; i++)
      {
          if(pre[i] == -1 && cap[t][i] > 0)
          {
              pre[i] = t;
              flow[i] = min(flow[t],cap[t][i]);
              q.push(i);
          }
      }
  }
  if(pre[ed] == -1)  return -1;
  else              return flow[ed];
}


int EK(int st, int ed)
{
    int res = 0; //the augmenting flow
    int sum = 0; //the max_flow
    while((res = bfs(st,ed)) != -1)//argumenting path
    {
        int k = ed;
        while(k != st)
        {
            int f = pre[k];
            cap[f][k] -= res;
            cap[k][f] += res;//reversible edge
            k = f;
        }
        sum += res;
    }
    return sum;
}
int main()
{
   int s,t,c;
   scanf("%d%d",&n,&m);
   memset(cap,0,sizeof(cap));
   for(int i = 0; i < m; i++)
   {
       scanf("%d%d%d",&s,&t,&c);
       cap[s][t] = c;
   }
   int ans = EK(1,n);
   printf("%d\n",ans);
   return 0;
}

Dinic

//Max_flow
//@2018/05/04 Friday
//Dinic  O(n^2 * m)  O(m*3*2)
//by Tawn
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1000 + 10;
const int maxm = 1e6 + 10;

int n,m;
int l[maxn];//记录层数
int h[maxn];//链式前向星
int cur[maxn];
int tot = 0;

struct edge
{
  int to;
  int c;
  int next;
  edge(int x = 0, int y = 0, int z = 0) : to(x), c(y), next(z) {}
 }es[maxm*2];//记录边 注意是2倍

void add_edge(int u, int v, int c)
{
    es[tot] = edge(v,c,h[u]);
    h[u] = tot++;
    es[tot] = edge(u,0,h[v]);
    h[v] = tot++;
}

bool bfs(int s, int t)
{
   memset(l,0,sizeof(l));
   l[s] = 1;
   queue <int> q;
   q.push(s);
   while(!q.empty())
   {
    int u = q.front();
    q.pop();
    if(u == t)  return true;
    for(int i = h[u]; i != -1; i = es[i].next)
        {
         int v = es[i].to;
         if(!l[v] && es[i].c) {l[v] = l[u] + 1; q.push(v);}
        }
   }
   return false;
}

int dfs(int x, int t, int mf)
{
    if(x == t) return mf;
    int ret = 0;
    for(int &i = cur[x]; i != -1; i = es[i].next)
    {
      if(es[i].c && l[x] == l[es[i].to] - 1)
      {
        int f = dfs(es[i].to,t,min(es[i].c,mf - ret));
        es[i].c -= f;
        es[i^1].c += f;
        ret += f;
        if(ret == mf) return ret;
      }
    }
    return ret;
}

int dinic(int s, int t)
{
  int ans = 0;
  while(bfs(s,t))
  {
   for(int i = 0; i <= t; i++) cur[i] = h[i];
   ans += dfs(s,t,INF);
   }
  return ans;
}


int main()
{
   while(~scanf("%d%d",&n,&m))
   {
   tot = 0;
   memset(h,-1,sizeof(h));
   int u,v,c;
   for(int i = 0; i < m; i++)
   {
    scanf("%d%d%d",&u,&v,&c);
    add_edge(u,v,c);
   }
   int ans = dinic(1,n);
   printf("%d\n",ans);
   }
   return 0;   
}

SAP

//Max_flow
//@2018/05/04 Friday
//SAP  O(n^2 * m)  O(m*3*2)
//by Tawn
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 200 + 10;
const int maxm = 200 + 10;

int n,m;
int head[maxn];//链式前向星
int tot = 0;

struct edge
{
 int to;
 int c;
 int next;
 edge(int x = 0, int y = 0, int z = 0) : to(x), c(y), next(z) {}
}es[maxm*2];//记录边 注意是2倍

void add_edge(int u, int v, int c)
{
   es[tot] = edge(v,c,head[u]);
   head[u] = tot++;
}


int SAP(int s, int t)
{
   int numh[maxn],h[maxn],ce[maxn],pre[maxn];
   //numh 记录gap优化的统计高度数量数组,h 距离标号数组,ce 当前弧,pre前驱数组
   int f, ans = 0, u, temp, neck, i; //初始化最大流为0
   memset(h,0,sizeof(h));
   memset(numh,0,sizeof(numh));
   memset(pre,-1,sizeof(pre));
   for(i = 1; i <= n; i++)  ce[i] = head[i];
   numh[0] = n;
   u = s;
   while(h[s] < n)
   {
       //寻找增广路
       if(u == t)
       {
           f = INF;
           for(i = s; i != t; i = es[ce[i]].to)
           {
               if(f > es[ce[i]].c)
               {
                   neck = i;
                   f = es[ce[i]].c;
               }
           }
           for(i = s; i != t; i = es[ce[i]].to)
           {
               temp = ce[i];
               es[temp].c -= f;
               es[temp^1].c += f;
           }
           ans += f;
           u = neck;
       }

       //寻找可行弧
       for(i = ce[u]; i != -1; i = es[i].next)
           if(es[i].c && h[u] == h[es[i].to] + 1)  break;

      //寻找增广路
       if(i != -1)
       {
           ce[u] = i;
           pre[es[i].to] = u;
           u = es[i].to;
       }
       else
       {
           if(!--numh[h[u]]) break; //gap optimization
           ce[u] = head[u];
           for(temp = n, i = head[u]; i != -1; i = es[i].next)
               if(es[i].c)  temp = min(temp, h[es[i].to]);

           h[u] = temp + 1;
           ++numh[h[u]];
           if(u != s) u = pre[u];//重标号并且从当前点前驱重新增广
       }

   }
   return ans;
}

int main()
{
  while(~scanf("%d%d",&n,&m))
  {
  tot = 0;
  memset(head,-1,sizeof(head));
  int u,v,c;
  for(int i = 0; i < m; i++)
  {
   scanf("%d%d%d",&u,&v,&c);
   add_edge(u,v,c);
   add_edge(v,u,0);//增加反向边
  }
  int ans = SAP(1,n);
  printf("%d\n",ans);
  }
  return 0;   
}

最小费用最大流

EK & Spfa

#include <bits/stdc++.h>

using namespace std;
const int maxn = 100  + 100;
const int maxm = 100 + 100;
const int INF = 0x3f3f3f3f;
typedef long long LL;
typedef pair<int,int> P;
//const LL mod = 1e9 + 7;

#define PI 3.1415926
#define sc(x)  scanf("%d",&x)
#define pf(x)  printf("%d",x)
#define pfn(x) printf("%d\n",x)
#define pfln(x) printf("%lld\n",x)
#define pfs(x) printf("%d ",x)
#define rep(n) for(int i = 0; i < n; i++)
#define per(n) for(int i = n-1; i >= 0; i--)
#define mem(a,x) memset(a,x,sizeof(a))

struct edge
{
  int from,to,cap,flow,cost;
  edge(int from,int to, int cap, int flow, int cost) : from(from),to(to),cap(cap),flow(flow),cost(cost) {}
};

int tot = 0;
vector<edge> E;
vector<int> G[maxn];
int inq[maxn];
int d[maxn];
int p[maxn];
int a[maxn];


void addedge(int from, int to, int cap, int cost)
{
  E.push_back(edge(from,to,cap,0,cost));
  E.push_back(edge(to,from,0,0,-cost));
  tot = E.size();
  G[from].push_back(tot-2);
  G[to].push_back(tot-1);
}

bool spfa(int s, int t, int &flow, int &cost)
{
  mem(d,INF);
  mem(inq,0);
  d[s] = 0,inq[s] = 1; p[s] = 0, a[s] = INF;
  queue<int> Q;
  Q.push(s);
  while(!Q.empty())
  {
    int u = Q.front();Q.pop();
    inq[u] = 0;
    for(int i = 0; i < G[u].size(); i++)
    {
      edge e = E[G[u][i]];
      if(e.cap > e.flow && d[e.to] > d[u] + e.cost)
      {
        d[e.to] = d[u] + e.cost;
        p[e.to] = G[u][i];
        a[e.to] = min(a[u],e.cap - e.flow);
        if(!inq[e.to]) {Q.push(e.to); inq[e.to] = 1;}
      }
    }
  }

  if(d[t] == INF) return false;
  flow += a[t];
  cost += d[t] * a[t];
  int u = t;
  while(u != s)
  {
    E[p[u]].flow += a[t];
    E[p[u]^1].flow -= a[t];
    u = E[p[u]].from;
  }
  return true;
}

int mcf(int s, int t)
{
  int flow = 0,cost = 0;
  while(spfa(s,t,flow,cost)) ;
  return cost;
}

int main()
{
   //记得初始化各个变量
   int s,t;
   mcf(s,t);
}

上下界网络流

无源汇可行流

// 此处省略dinic板子

int B[maxm];
int in[maxn];

int main()
{

 int T;
 scanf("%d",&T);
 while(T--)
 {
   scanf("%d%d",&n,&m);
   tot = 0;
   memset(h,-1,sizeof(h));
   memset(in,0,sizeof(in));
   for(int i = 0; i < m; i++)
   {
      int u,v,b,c;
      scanf("%d%d%d%d",&u,&v,&b,&c);
      add_edge(u,v,c-b);
      B[i] = b;
      in[u] -= b;
      in[v] += b;
   }
  int s = 0, t = n+1;
  int res = 0;
  for(int i = 1; i <= n; i++)
  {
     if(in[i] > 0) {res += in[i]; add_edge(s,i,in[i]);}
     if(in[i] < 0) {add_edge(i,t,-in[i]);}
  }
  int ans = dinic(s,t);
  if(ans != res) printf("NO\n");
  else {
      printf("YES\n");
      for(int i = 0; i < m; i++)
      {
              printf("%d\n",B[i] + es[2*i+1].c);
      }
    }
 }
 return 0;
}

有源汇可行流

新增超级源点超级汇点 && t -INF-> s

//2018/10/04
//by Tawn in JNU
// 此处省略dinic板子
int B[maxn][30];
int C[maxn][30];
int in[maxn];

int main()
{
 //freopen("in.txt","r",stdin);
 int T;
 scanf("%d",&T);
 while(T--)
 {
   scanf("%d%d",&n,&m);
   int s = n+m+1, t = n+m+2,ss = 0,tt=t+1;
   tot = 0;
   memset(h,-1,sizeof(h));
   memset(in,0,sizeof(in));

  add_edge(t,s,INF);
  int res = 0;  
  for(int i = 1; i <= t; i++)
   {
   if(in[i] > 0)  {add_edge(ss,i,in[i]);res += in[i];}
   if(in[i] < 0)  add_edge(i,tt,-in[i]);
   }

  int ans = dinic(ss,tt);
  if(ans != res) printf("IMPOSSIBLE\n");
  else {
         int k = 1;
     for(int i = 1; i <= n; i++)
       for(int j = 1; j <= m; j++,k+=2)
         printf("%d%c",B[i][j]+es[k].c, j == m? '\n':' ');
    }
 printf("\n");
 }
 return 0;
}

有源汇最大流

// 此处省略dinic板子

int in[maxn];
int g[maxn];
int d[maxn];
int b[maxm*2];//该边下界

int main()
{
   while(~scanf("%d%d",&n,&m))
  {
   memset(h,-1,sizeof(h));
   memset(in,0,sizeof(in));
   tot = 0;
   int s = n+m+1,t = n+m+2,ss = 0,tt = t+1,totc = 0;

   add_edge(t,s,INF);

   int res = 0;
   for(int i = 1; i <= t; i++)
   {
      if(in[i] > 0) {add_edge(ss,i,in[i]); res += in[i];}
      if(in[i] < 0) add_edge(i,tt,-in[i]);
   }
   int ans = dinic(ss,tt);
   if(ans == res)
   {
      add_edge(t,s,-INF);
      int _ans = dinic(s,t);
      printf("%d\n",_ans);
      for(int i = 1; i < totc; i += 2)
        printf("%d\n",es[i].c + b[i]);
   }
   else printf("-1\n");
   printf("\n");
  }
   return 0;
}               

有源汇最小流

// 此处省略dinic板子

int in[maxn];

int main()
{
  while(~scanf("%d%d",&n,&m) && (n||m))
  {
    tot = 0;
    memset(h,-1,sizeof(h));
    memset(in,0,sizeof(in));
    int s = n+1, t = n+2, ss = 0, tt = t+1;
    for(int i = 0; i < m; i++)
    {
      string s1,s2;
      int u,v,p;
      scanf("%d%d%d",&u,&v,&p);
      in[u] -= p;
      in[v] += p;
      add_edge(u,v,INF);
    }
    int res = 0;
    for(int i = 1; i <= t; i++)
    {
      if(in[i] > 0) {res += in[i]; add_edge(ss,i,in[i]);}
      if(in[i] < 0)  add_edge(i,tt,-in[i]);
    }
    int ans = dinic(ss,tt);
    add_edge(t,s,INF);
    ans += dinic(ss,tt);
    if(res == ans)  printf("%d\n",es[h[t]^1].c);
    else printf("impossible\n");
  }
  return 0;
}

二分图

判断是否二分图

bool g[maxn][maxn];
int col[maxn];
//利用0,1染色,层序遍历,用同色则为false
bool bfs(int n)
{
    memset(col,-1,sizeof(col));
    for(int i = 0; i < n; i++)
    {
        if(col[i] != -1) continue;
        queue<int> q;
        col[i] = 1;
        q.push(i);
        while(!q.empty())
        {
            int from = q.front();
            q.pop();
            for(int to = 0; to < n; to++)
            {
                if(g[from][to] && col[to] == -1)
                {
                    col[to] = !col[from];
                    q.push(to);
                }
                if(g[from][to] && col[to] == col[from])
                    return false;
            }
        }
    }
    return true;
}

二分图匹配

  • 匈牙利算法
bool g[maxn][maxn];
bool vis[maxn];
int line[maxn];
//寻找增广路O(VE)
bool dfs(int u,int n)
{
    for(int v = 0; v < n; v++)
    {
        if(g[u][v] && !vis[v])
        {
        vis[v] = true;
        if(line[v] == -1 || dfs(line[v],n))
        {line[v] = u;return true;}
        }
    }
    return false;
}

int Max_match(int n,int p)
{
    int all = 0;
    memset(line,-1,sizeof(line));
    for(int i = 0; i < p; i++)
    {
        memset(vis,false,sizeof(vis));
        if(dfs(i,n)) all++;
    }
    return all;
}
  • Hopcroft-Carp
// O(sqrt(n)*e)
typedef long long LL;
using namespace std;
const int maxn = 3010;
const int INF = 0x3f3f3f3f;
bool g[maxn][maxn];
bool vis[maxn];
int dx[maxn],dy[maxn];
int Mx[maxn],My[maxn];
int dis;
int GM[maxn][2];
int GN[maxn][2];
int speed[maxn];
double _time;
int M,N;

bool bfs(int n,int m)
{
    dis = INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    queue <int> que;
    for(int i = 0; i < n; i++)
    {
        if(Mx[i] == -1)
            {
                que.push(i);
                dx[i] = 0;
            }
    }

    while(!que.empty())
    {
        int u = que.front();
        que.pop();
        if(dx[u] > dis)  break;
        for(int v = 0; v < m; v ++)
        {
            if(g[u][v] && dy[v] == -1)
            {
                dy[v] = dx[u] + 1;
                if(My[v] == -1) dis = dy[v];
                else {
                    dx[My[v]] = dy[v] + 1;
                    que.push(My[v]);
                }
            }
        }
    }
    return dis != INF;
}


bool dfs(int u,int m)
{
   for(int v = 0; v < m; v++)
   {
      if(g[u][v] && !vis[v] && dy[v] == dx[u] + 1)
      {
          vis[v] = true;
          if(My[v] != -1 && dy[v] == dis) continue;

          if(My[v] == -1 || dfs(My[v],m))
          {
              My[v] = u;
              Mx[u] = v;
              return true;
          }
      }
   }
    return false;
}



int Max_match(int n,int m)
{
    int all = 0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(bfs(n,m))
    {
       memset(vis,false,sizeof(vis));
       for(int i = 0; i < n; i++)
            if(Mx[i] == -1 && dfs(i,m))
            all++;
    }
    return all;
}

最小路径覆盖

  • 最小路径覆盖 = n-二分图最大匹配数 拆点建二分图
/*
     int f = dfs(es[i].to,t,min(es[i].c,mf - ret));
        if(f) nxt[x] = es[i].to;
*/

int nxt[maxn];//记录路径
int vis[maxn];//查找路径时标记点

void solve()
{
   scanf("%d%d",&n,&m);
   tot = 0;
   memset(nxt,-1,sizeof(nxt));
   memset(h,-1,sizeof(h));
   memset(vis,false,sizeof(vis));

   int s = 0, t = 2*n+1,u,v;
   for(int i = 1; i <= n; i++)
     add_edge(s,i,1),add_edge(i+n,t,1);

   for(int i = 0; i < m; i++)
   {
       scanf("%d%d",&u,&v);
       add_edge(u,v+n,1);
   }

   int res = dinic(s,t);

   printf("%d\n",n-res); //最小路径覆盖 = n - 二分图最大匹配
   for(int i = 1; i <= n; i++)
       if(!vis[i])
       {
           vector <int> v;
           int t = i;
           do{
            vis[t] = true;
            v.push_back(t);
            t = nxt[t]-n;
           }while(t > 0);
           for(int j = 0; j < v.size(); j++)
             printf("%d%c",v[j], j == v.size() -1 ? '\n' : ' ');
       }
}

补充

二分图最小点权覆盖集 = 最小割 = 最大流, 最大点权独立集 = 总权 - 最小点权覆盖集


  目录