NOIp 2018 前的图论板子总结

∥☆過路亽.° 提交于 2020-04-07 07:25:05

存图

存边

直接开一个结构体数组存边

struct Edge {
  int begin, end, weight;
} edge[10010];
int edge_count;
inline void AddEdge(const int &u, const int &v, const int &w) {
  edge[edge_count++] = Edge {u, v, w};
}

应用:

  1. Kruskal's algorithm

Adjacency matrix

用二维数组adj[i][j]表示\(i\)\(j\)的关系

int adj[1010][1010];
#define ADD_EDGE(u, v, w) adj[u][v] = w

应用:

  1. Floyd-Warshall algorithm
  2. Hangarian algorithm
  3. Kuhn-Munkres algorithm

Adjacency list

有几种形式, 以adj[i]表示以\(i\)为开头的边

应用: 各种图论算法

vector

优点: 访问方便, 存图方便

缺点: 消耗空间, 容易\(MLE\); 删边速度慢

struct Edge {
  int destination, weight;
};
std::vector<Edge> adj[1010];
加边
#define ADD_EDGE(u, v, w) adj[u].push_back(Edge {v, w})
访问
for (register int i(0); i < adj[u].size(); ++i) {
  adj[u][i]...
  ...
}

for (auto i : adj[u]) {
  i...
  ...
}
list

优点: 添边删边速度快

缺点: 不易访问; 容易\(MLE\)

struct Edge {
  int destination, weight;
};
std::list<Edge> adj[1010];
加边
#define ADD_EDGE(u, v, w) adj[u].push_back(Edge {v, w})
访问
for (register std::list<Edge>::iterator i = adj[u].begin(); i != adj[u].end(); ++i) {
  *i...
  ...
}

for (auto i : adj[u]) {
  i...
  ...
}

链式前向星

优点: 空间重复利用, 通常不会\(MLE\), 而且很快

缺点: 开小了会\(WA\), 开大了会\(TLE\), 有时还会\(RE\)

应用: 各种图论算法

struct Edge {
  int destination, weight, next;
} edge[10010];
int head[1010], edge_count;
加边
inline void AddEdge(const int &u, const int &v, const int &w) {
  edge[edge_count] = Edge {v, w, head[u]},
  head[u] = edge_count++;
}
访问
for (register int i(head[u]); i != -1; i = edge[i].next) {
  edge[i]...
  ...
}

最小生成树

题目链接: Luogu P3366 【模板】最小生成树

Kruskal's algorithm

将边以权值从小到大排序遍历, 用并查集加边, 同时维护答案。

特点: 适合稀疏图

时间复杂度: \(\Theta(|E| \lg |V|)\)

#include <cstdio>
#include <algorithm>
struct Edge {
  int begin, end, weight;
  inline bool operator <(const Edge &another) const {
    return this->weight < another.weight;
  }
} edge[200010];
int unions[5010];
int n, m;
inline int Find(const int&);
inline int Kruskal();
int main(int argc, char const *argv[]) {
  scanf("%d %d", &n, &m);
  for (register int i(0); i <= n; ++i) {
    unions[i] = i;
  }
  for (register int i(0), u, v, w; i < m; ++i) {
    scanf("%d %d %d", &u, &v, &w),
    edge[i] = Edge {u, v, w};
  }
  register int ans(Kruskal());
  if (ans) printf("%d\n", ans);
  else puts("orz");
  return 0;
}
inline int Find(const int &x) {
  return unions[x] == x ? x : (unions[x] = Find(unions[x]));
}
inline int Kruskal() {
  register int ret(0);
  std::sort(edge, edge + m);
  for (register int i(0), countt(0), u, v; i < m; ++i) {
    u = Find(edge[i].begin), v = Find(edge[i].end);
    if (u != v) {
      unions[v] = u,
      ++countt,
      ret += edge[i].weight;
      if (countt == n - 1) {
        return ret;
      }
    }
  }
  return 0;
}

Prim's algorithm

随机选择一个结点作为树, 每次找与这棵树相连且不构成环的最小边加入进来, 形成生成树。

可以使用优先队列优化。

特点: 适合稠密图

时间复杂度: \(\Theta(|E| \lg |V|)\)

#include <cstdio>
#include <queue>
#include <vector>
#include <cstring>
struct Edge {
  int destination, weight;
};
std::vector<Edge> adj[5010];
int distance[5010], vis[5010];
int n, m;
inline int Prim();
int main(int argc, char const *argv[]) {
  memset(distance, 0x3f, sizeof(distance));
  scanf("%d %d", &n, &m);
  for (register int i(0), u, v, w; i < m; ++i) {
    scanf("%d %d %d", &u, &v, &w);
    adj[u].push_back(Edge {v, w}),
    adj[v].push_back(Edge {u, w});
  }
  register int ans(Prim());
  if (ans) printf("%d\n", ans);
  else puts("orz");
  return 0;
}
inline int Prim() {
  register int ret(0), countt(0);
  distance[1] = 0;
  std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int> >, std::greater<std::pair<int, int> > > Q;
  Q.push(std::pair<int, int>(0, 1));
  while (!Q.empty()) {
    register int w(Q.top().first), u(Q.top().second);
    Q.pop();
    if (!vis[u]) {
      ++countt,
      ret += w,
      vis[u] = 1;
      for (auto i : adj[u]) {
        if (i.weight < distance[i.destination]) {
          distance[i.destination] = i.weight, Q.push(std::pair<int, int>(distance[i.destination], i.destination));
        }
      }
    }
  }
  return ret * (countt == n);
}

强连通分量

题目链接: USACO 06 Jan. The Cow Prom

Tarjan's strongly connected components algorithm

是基于对图深度优先搜索(DFS)的算法, 每个强连通分量为搜索树中的一棵子树。搜索时, 把当前搜索树中未处理的节点加入一个堆栈, 回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

时间复杂度: \(\Theta(|V|+|E|)\)

#include <cstdio>
#include <vector>
#include <stack>
#include <algorithm>
std::vector<int> adj[10010];
int dfn[10010], low[10010], color_count[10010], indexx, countt;
bool instack[10010];
int n, m, ans;
std::stack<int> S;
inline int Tarjan(const int&);
int main(int argc, char const *argv[]) {
  scanf("%d %d", &n, &m);
  for (register int i(0), u, v;i < m; ++i) {
    scanf("%d %d", &u, &v);
    adj[u].push_back(v);
  }
  for (register int i(1); i <= n; ++i) {
    if (!dfn[i]) {
      Tarjan(i);
    }
  }
  for (register int i(1); i <= countt; ++i) {
    if (color_count[i] > 1) ++ans;
  }
  printf("%d\n", ans);
  return 0;
}
inline void Tarjan(const int &cur) {
  dfn[cur] = low[cur] = ++indexx;
  S.push(cur), instack[cur] = 1;
  for (auto i : adj[cur]) {
    if (!dfn[i]) {
      Tarjan(i);
      low[cur] = std::min(low[cur], low[i]);
    } else if (instack[i]) {
        low[cur] = std::min(low[cur], low[i]);
    }
  }
  if (dfn[cur] == low[cur]) {
    ++countt;
    while (S.top() != cur) {
      register int node(S.top());
      S.pop(),
      instack[node] = 0,
      ++color_count[countt];
    }
    S.pop(),
    ++color_count[countt],
    instack[cur] = 0;
  }
}

二分图最大匹配

题目链接: Luogu P3386 【模板】二分图匹配

Hungarian algorithm

通过寻找增广路来计算最大匹配值

时间复杂度:

  1. Adjacency matrix: \(\Theta(n^3)\)
  2. Adjacency list: \(\Theta(nm)\)
#include <cstdio>
#include <vector>
#include <cstring>
std::vector<int> adj[1010];
int n, m, e;
int vis[1010], mate[1010];
inline bool Match(const int&);
inline int Hungarian();
int main(int argc, char const *argv[]) {
  scanf("%d %d %d", &n, &m ,&e);
  for (register int i(0), u, v; i < e; ++i) {
    scanf("%d %d", &u, &v);
    if (u <= n && v <= m) {
      adj[u].push_back(v);
    }
  }
  printf("%d\n", Hungarian());
  return 0;
}
inline int Hungarian() {
  register int ret(0);
  for (register int i(1); i <= n; ++i) {
    memset(vis, 0, sizeof(vis));
    ret += Match(i);
  }
  return ret;
}
inline bool Match(const int &cur) {
  for (auto i : adj[cur]) {
    if (!vis[i]) {
      vis[i] = 1;
      if (!mate[i] || Match(mate[i])) {
        mate[i] = cur;
        return true;
      }
    }
  }
  return false;
}

二分图最佳匹配

Kuhn-Munkres algorithm

是一种逐次修改可行顶标的方法,使之对应的等价子图逐次增广(增加边),最后出现完备匹配.

时间复杂度: \(\Theta(n^3)\)

#include <cstdio>
#include <cstring>
int nl, nr;
int adj[310][310];
int mate[310], dist_l[310], dist_r[310];
int slack[310];
bool vis_l[310], vis_r[310];
int n;
inline bool BestMatch(const int&);
inline int KuhnMunkres();
int main(int argc, char const *argv[]) {
  while(~scanf("%d", &n)) {
    for (register int i(0); i < n; ++i) {
      for(register int j(0); j < n; ++j) {
        scanf("%d", &adj[i][j]);
      }
    }
    nl = nr = n;
    printf("%d\n" ,KuhnMunkres());
  }
  return 0;
}
inline bool BestMatch(const int &cur) {
  vis_l[cur] = true;
  for (register int r(0); r < nr; ++r) {
    if (!vis_r[r]) {
      register int tmp(dist_l[cur] + dist_r[r] - adj[cur][r]);
      if (!tmp) {
        vis_r[r] = true;
        if(!~mate[r] || BestMatch(mate[r])){
          mate[r] = cur;
          return true;
        }
      }
      else if(slack[r] > tmp)
        slack[r] = tmp;
    }
  }
  return false;
}
inline int KuhnMunkres() {
  memset(mate, -1, sizeof(mate));
  memset(dist_r, 0, sizeof(dist_r));
  for (register int i(0); i < nl; ++i) {
    dist_l[i] = 0x80000000;
    for (register int j(0); j < nr; ++j) {
      if (adj[i][j] > dist_l[i]) {
        dist_l[i] = adj[i][j];
      }
    }
  }
  for (register int l(0); l < nl; ++l) {
    for (register int i(0); i < nr; ++i) {
      slack[i] = 0x7fffffff;
    }
    while(true) {
      memset(vis_l, false, sizeof vis_l ),
      memset(vis_r, false, sizeof vis_r );
      if (BestMatch(l)) break;
      register int d(0x7fffffff);
      for (register int i(0); i < nr; ++i){
        if (!vis_r[i] && d > slack[i]) {
          d = slack[i];
        }
      }
      for (register int i(0); i < nl; ++i){
        if (vis_l[i]) {
          dist_l[i] -= d;
        }
      }
      for (register int i(0); i < nr; ++i) {
        if (vis_r[i]) dist_r[i] += d;
        else slack[i] -= d;
      }
    }
  }
  register int res(0);
  for (register int i(0); i < nr; ++i) {
    if(~mate[i]) {
      res += adj[mate[i]][i];
    }
  }
  return res;
}

最近公共祖先

Heavy path decomposition

学倍增和Tarjan的时候没好好学现在只会树链剖分

题目链接: Luogu P3379 【模板】最近公共祖先(LCA)

时间复杂度:

  1. Dfs1: \(\Theta(n)\)
  2. Dfs2: \(\Theta(n)\)
  3. LowestCommonDivisor: \(\Theta(\lg n)\)

模板题要写读入优化过

#include <cstdio>
#include <vector>
#include <algorithm>
int f(-1);
inline char GetCharacter() {
    static char buf[2000000], *p1 = buf, *p2 = buf;
    return (p1 == p2) &&
           (p2 = (p1 = buf) + fread(buf, 1, 2000000, stdin), p1 == p2) ? 
           EOF : *p1++;
}
#define IS_DIGIT(c) (c >= '0' && c <= '9')
inline void Read(int &x) {
    f = 1, x = 0;
    static char c = GetCharacter();
    while (!IS_DIGIT(c)) {
        if (c == '-') f = -1;
        c = GetCharacter();
    }
    while (IS_DIGIT(c)) x = x * 10 + c - '0', c = GetCharacter();
    x *= f;
}
#undef IS_DIGIT
std::vector<int> adj[500010];
int heavy[500010], size[500010], father[500010], top[500010], depth[500010];
int root, n, m;
int Dfs1(const int &cur, const int &fathernode) {
  size[cur] = 1;
  father[cur] = fathernode;
  depth[cur] = depth[fathernode] + 1;
  for (auto i : adj[cur]) {
    if (i != fathernode) {
      size[cur] += Dfs1(i, cur);
      if (size[i] > size[heavy[cur]]) heavy[cur] = i;
    }
  }
  return size[cur];
}
void Dfs2(const int &cur, const int &topnode) {
  top[cur] = topnode;
  if (heavy[cur]) {
    Dfs2(heavy[cur], topnode);
    for (auto i : adj[cur]) {
      if (heavy[cur] != i && father[cur] != i) {
        Dfs2(i, i);
      }
    }
  }
}
inline int LowestCommonAncestor(const int &x, const int &y) {
  int a(x), b(y);
  while (top[a] != top[b]) {
    if (depth[top[a]] < depth[top[b]]) std::swap(a, b);
    a = father[top[a]];
  }
  return depth[a] > depth[b] ? b : a;
}
int main(int argc, char const *argv[]) {
  Read(n), Read(m), Read(root);
  for (int i(1), u, v; i < n; ++i) {
    Read(u), Read(v);
    adj[u].push_back(v), adj[v].push_back(u);
  }
  Dfs1(root, root);
  Dfs2(root, root);
  while (m--) {
    int u, v;
    Read(u), Read(v);
    printf("%d\n", LowestCommonAncestor(u, v));
  }
  return 0;
}

最短路

Floyd-Warshall algorithm

枚举每一个结点作为中间点, 再枚举每一个起点和终点, 可以松弛就进行松弛。

时间复杂度: \(\Theta(n^3)\)

题目链接: HDU 2544 最短路

#include <cstdio>
#include <cstring>
#include <algorithm>
int adj[110][110];
int n, m;
int main(int argc, char **argv) {
  while (~scanf("%d %d", &n, &m) && (n || m)) {
    memset(adj, 0x3f, sizeof(adj));
    for (register int i(1), u, v, w; i <= m; ++i) {
      scanf("%d %d %d", &u, &v, &w);
      adj[u][v] = adj[v][u] = std::min(adj[u][v], w);
    }
    for (register int i(1); i <= n; ++i) adj[i][i] = 0;
    for (register int k(1); k <= n; ++k) {
      for (register int i(1); i <= n; ++i) {
        for (register int j(1); j <= n; ++j) {
          adj[j][i] = adj[i][j] = std::min(adj[i][j], adj[i][k] + adj[k][j]);
        }
      }
    }
    printf("%d\n", adj[1][n]);
  }
  return 0;
}

Shortest Path Faster Algorithm(SPFA)

是Bellman-Ford algorithm的改进, 通常情况下不会出什么问题, 但精心设计的稠密图可以轻易卡掉SPFA, 使用需谨慎。

平均时间复杂度: \(\Theta(|E|)\)

理论上界: \(\Theta(|V||E|)\)

题目链接: Luogu P3371 【模板】单源最短路径(弱化版)

#include <deque>
#include <cstdio>
#include <vector>
#include <cstring>
struct Edge {
  int destination, weight;
};
std::vector<Edge> adj[10010];
int n, m, s;
int distance[10010];
bool vis[10010];
inline void ShortestPathFasterAlgorithm();
int main(int argc, char const *argv[]) {
  scanf("%d %d %d", &n, &m, &s);
  for (register int i(0), u, v, w; i < m; ++i) {
    scanf("%d %d %d", &u, &v, &w);
    adj[u].push_back(Edge {v, w});
  }
  ShortestPathFasterAlgorithm();
  for (register int i(1); i <= n; ++i) {
    printf("%d ", distance[i] == 0x3f3f3f3f ? 2147483647 : distance[i]);
  }
  return 0;
}
inline void ShortestPathFasterAlgorithm() {
  memset(distance, 0x3f, sizeof(distance));
  distance[s] = 0;
  std::deque<int> Q;
  Q.push_back(s);
  vis[s] = 0;
  while (!Q.empty()) {
    register int cur(Q.front());
    Q.pop_front();
    vis[cur] = 0;
    for (auto i : adj[cur]) {
      if (distance[i.destination] > distance[cur] + i.weight) {
        distance[i.destination] = distance[cur] + i.weight;
        if (!vis[i.destination]) {
          if (distance[i.destination] < distance[Q.front()]) Q.push_front(i.destination);
          else Q.push_back(i.destination);
          vis[i.destination] = 1;
        }
      }
    }
  }
}

Dijkstra's algorithm

与最小生成树的Prim's algorithm相像, 主要思想为贪心, 适用于稠密图。

通常情况下使用SPFA, 如果数据卡SPFA可以尝试堆优化的Dijkstra's algorithm。

时间复杂度: \(\Theta((|E|)\lg |E|)\)

题目链接: Luogu P4779 【模板】单源最短路径(标准版)

#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
std::vector<std::pair<int, long long> > adj[100010];
long long distance[100010];
int vis[100010];
int n, m, s;
inline void Dijkstra() {
  register std::priority_queue<std::pair<long long, int>, std::vector<std::pair<long long, int> >, std::greater<std::pair<long long, int> > > Q;
  memset(distance, 0x3f, sizeof(distance));
  distance[s] = 0;
  Q.push(std::pair<long long, int>(0ll, s));
  while (!Q.empty()) {
    register int cur(Q.top().second);
    Q.pop();
    if (!vis[cur]) {
      vis[cur] = 1;
      for (auto i : adj[cur]) {
        if (distance[i.first] > distance[cur] + i.second) {
          distance[i.first] = distance[cur] + i.second;
          Q.push(std::pair<long long, int>(distance[i.first], i.first));
        }
      }
    }
  }
}
int main(int argc, char const *argv[]) {
  scanf("%d %d %d", &n, &m, &s);
  for (register int i(1), u, v, w; i <= m; ++i) {
    scanf("%d %d %d", &u, &v, &w);
    adj[u].push_back(std::pair<int, long long>(v, (long long)(w)));
  }
  Dijkstra();
  for (register int i(1); i <= n; ++i) {
    printf(i == n ? "%lld\n" : "%lld ", distance[i]);
  }
  return 0;
}

最大流

Edmonds-Karp algorithm

最大流的基础方法, 思想非常简单:

每次BFS找到一条增广路进行增广, 记录该路径上可增广的最大流, 在图中减去即可。、

时间复杂度: \(\Theta(|V||E|^2)\)

题目链接: USACO4.2 Drainage Ditches

#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>
#include <cstring>
#define INF 2147483647
int predeccessor[210], flow[210];
int G[210][210];
int n, m;
inline int Bfs(const int &S, const int &T) {
  register std::queue<int> q;
  memset(predeccessor, -1, sizeof(predeccessor));
  predeccessor[S] = 0;
  flow[S] = INF;
  q.push(S);
  while (!q.empty()) {
    register int current(q.front());
    q.pop();
    if (current == T) {
      break;
    }
    for (register int i(1); i <= n; ++i) {
      if (G[current][i] && predeccessor[i] == -1) {
        predeccessor[i] = current;
        flow[i] = std::min(flow[current], G[current][i]);
        q.push(i);
      }
    }
  }
  return predeccessor[T] == -1 ? -1 : flow[T];
}
inline int EdmondsKarp(const int &S, const int &T) {
  register int ret(0), increase(0);
  while((increase = Bfs(S, T)) != -1) {
    register int current(T);
    while (current != S) {
      G[predeccessor[current]][current] -= increase;
      G[current][predeccessor[current]] += increase;
      current = predeccessor[current];
    }
    ret += increase;
  }
  return ret;
}
int main(int argc, char **argv) {
  scanf("%d %d", &m, &n);
  for (register int i(0), u, v, w; i < m; ++i) {
    scanf("%d %d %d", &u, &v, &w);
    G[u][v] += w;
  }
  printf("%d\n", EdmondsKarp(1, n));
  return 0;
}

Dinic's algorithm

首先利用BFS对网络进行分层, 然后利用DFS从前一层向后一层反复寻找增广路(利用回溯), 当这次DFS无法继续增广时, 重复BFS步骤, 直到BFS无法到达汇点时, 算法结束。

时间复杂度: \(\Theta(|V|^2 |E|)\)

题目链接: Luogu P3376 【模板】网络最大流

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
int n, m, s, t;
int depth[10010];
struct Edge {
  int destination, maxflow, next;
  Edge() {
    destination = maxflow = next = 0;
  }
} edge[200010];
int head[10010];
inline void AddEdge(const int &u, const int &v, const int &w) {
  static int edge_count(0);
  edge[edge_count].destination = v;
  edge[edge_count].maxflow = w;
  edge[edge_count].next = head[u];
  head[u] = edge_count++;
}
inline bool BreadthFirstSearch(const int &S, const int &T) {
  register std::queue<int> q;
  memset(depth, 0, sizeof(depth));
  depth[S] = 1;
  q.push(S);
  while (!q.empty()) {
    register int current(q.front());
    q.pop();
    for (register int i(head[current]); i != -1; i = edge[i].next) {
      if (edge[i].maxflow && !depth[edge[i].destination]) {
        depth[edge[i].destination] = depth[current] + 1;
        q.push(edge[i].destination);
      }
    }
  }
  return depth[T];
}
inline int DepthFirstSearch(const int &current, 
                            register int maxflow, 
                            const int &T) {
  if (current == T) return maxflow;
  for (register int i(head[current]); i != -1; i = edge[i].next) {
    if (depth[edge[i].destination] == depth[current] + 1 && edge[i].maxflow) {
      register int delta(DepthFirstSearch(edge[i].destination, 
                                          std::min(maxflow, edge[i].maxflow), 
                                          T));
      if (delta) {
        edge[i].maxflow -= delta;
        edge[i ^ 1].maxflow += delta;
        return delta;
      }
    }
  }
  return 0;
}
inline int Dinic(const int &S, const int &T) {
  register int ret(0), delta;
  while (BreadthFirstSearch(S, T)) {
    while ((delta = DepthFirstSearch(S, 0x3f3f3f3f, T))) {
      ret += delta;
    }
  }
  return ret;
}
int main(int argc, char **argv) {
  memset(head, -1, sizeof(head));
  scanf("%d %d %d %d", &n, &m, &s, &t);
  for (register int i(1), u, v, w; i <= m; ++i) {
    scanf("%d %d %d", &u, &v, &w);
    AddEdge(u, v, w), AddEdge(v, u, 0);
  }
  printf("%d\n", Dinic(s, t));
  return 0;
}

最小费用最大流

Dinic's algorithm

将费用看作路径长度, 把Dinic's algorithm中的BFS换成SPFA, 每次找费用最小的进行增广

时间复杂度: \(\Theta(|V|^2 |E|)\)

题目链接: Luogu P3381 【模板】最小费用最大流

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
int n, m, s, t;
struct Edge {
  int destination, maxflow, cost, next;
  Edge() {
    destination = maxflow = cost = next = 0;
  }
} edge[100010];
int head[5010];
int distance[5010], pre_node[5010], pre_edge[5010], flow[5010], vis[5010];
int maxflow, mincost;
inline void AddEdge(const int &u, const int &v, const int &w, const int &c) {
  static int edge_count(0);
  edge[edge_count].destination = v;
  edge[edge_count].maxflow = w;
  edge[edge_count].cost = c;
  edge[edge_count].next = head[u];
  head[u] = edge_count++;
}
inline bool ShortestPathFasterAlgorithm(const int &S, const int &T) {
  memset(distance, 0x3f, sizeof(distance));
  memset(flow, 0x3f, sizeof(flow));
  memset(vis, 0, sizeof(vis));
  register std::queue<int> q;
  q.push(S);
  vis[S] = 1, distance[S] = 0, pre_node[T] = -1;
  while (!q.empty()) {
    register int current(q.front());
    q.pop();
    vis[current] = 0;
    for (register int i(head[current]); i != -1; i = edge[i].next) {
      if (edge[i].maxflow && 
          distance[edge[i].destination] > distance[current] + edge[i].cost) {
        distance[edge[i].destination] = distance[current] + edge[i].cost;
        pre_node[edge[i].destination] = current;
        pre_edge[edge[i].destination] = i;
        flow[edge[i].destination] = std::min(flow[current], edge[i].maxflow);
        if (!vis[edge[i].destination]) {
          vis[edge[i].destination] = 1;
          q.push(edge[i].destination);
        }
      }
    }
  }
  return pre_node[T] != -1;
}
inline void Dinic(const int &S, const int &T) {
  while (ShortestPathFasterAlgorithm(S, T)) {
    register int current(T);
    maxflow += flow[T];
    mincost += flow[T] * distance[T];
    while (current != S) {
      edge[pre_edge[current]].maxflow -= flow[T];
      edge[pre_edge[current] ^ 1].maxflow += flow[T];
      current = pre_node[current];
    }
  }
}
int main(int argc, char **argv) {
  memset(head, -1, sizeof(head));
  scanf("%d %d %d %d", &n, &m, &s, &t);
  for (register int i(0), u, v, w, c; i < m; ++i) {
    scanf("%d %d %d %d", &u, &v, &w, &c);
    AddEdge(u, v, w, c), AddEdge(v, u, 0, -c);
  }
  Dinic(s, t);
  printf("%d %d\n", maxflow, mincost);
  return 0;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!