最大流问题是网络流的经典类型之一,用处广泛,个人认为网络流问题最具特点的操作就是建反向边,这样相当于给了反悔的机会,不断地求增广路的,最终得到最大流
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) cout<<"["<<x<<","<<y<<"] " //#define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e6 + 3; const int Max = 1e5 + 10; struct Edge { int to, next, flow; //flow记录这条边当前的边残量 }edge[Max << 1]; int n, m, s, t; int head[Max], tot; bool vis[Max]; void init() { memset(head, -1, sizeof(head));tot = 0; } void add(int u, int v, int flow) { edge[tot].to = v; edge[tot].flow = flow; edge[tot].next = head[u]; head[u] = tot++; } //向图中增加一条容量为exp的边(增广路) int dfs(int u,int exp) { if (u == t) return exp; //到达汇点,当前水量全部注入 vis[u] = true; //表示已经到了过了 for(int i = head[u] ; i != -1 ;i = edge[i].next) { int v = edge[i].to; if(!vis[v] && edge[i].flow > 0) { int flow = dfs(v, min(exp, edge[i].flow)); if(flow > 0) //形成了增广路 { edge[i].flow -= flow; edge[i ^ 1].flow += flow; return flow; } } } return 0; //无法形成增广路的情况 } //求最大流 int max_flow() { int flow = 0; while(true) { memset(vis, 0, sizeof(vis)); int part_flow = dfs(s, inf); if (part_flow == 0) return flow; flow += part_flow; } } int main() { #ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF) { init(); for (int i = 1, u, v, flow;i <= m; i++) { scanf("%d%d%d", &u, &v, &flow); add(u, v, flow);add(v, u, 0); } printf("%d\n", max_flow()); } return 0; }
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) "["<<x<<","<<y<<"]" //#define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e6 + 3; const int Max = 1e5 + 10; struct Edge { int to, next, flow; //flow记录这条边当前的边残量 }edge[Max << 1]; int n, m, s, t; int head[Max], tot; int dis[Max]; void init() { memset(head, -1, sizeof(head));tot = 0; } void add(int u, int v, int flow) { edge[tot].to = v; edge[tot].flow = flow; edge[tot].next = head[u]; head[u] = tot++; } bool bfs() //判断图是否连通 { queue<int>q; memset(dis, -1, sizeof(dis)); dis[s] = 0; q.push(s); while (!q.empty()) { int u = q.front();q.pop(); for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (dis[v] == -1 && edge[i].flow > 0) //可以借助边i到达新的结点 { dis[v] = dis[u] + 1; //求顶点到源点的距离编号 q.push(v); } } } return dis[t] != -1; //确认是否连通 } int dfs(int u, int flow_in) { if (u == t) return flow_in; int flow_out = 0; //记录这一点实际流出的流量 for (int i = head[u]; i != -1;i = edge[i].next) { int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].flow > 0) { int flow_part = dfs(v, min(flow_in, edge[i].flow)); if (flow_part == 0)continue; //无法形成增广路 flow_in -= flow_part; //流出了一部分,剩余可分配流入就减少了 flow_out += flow_part; //记录这一点最大的流出 edge[i].flow -= flow_part; edge[i ^ 1].flow += flow_part; //减少增广路上边的容量,增加其反向边的容量 if (flow_in == 0) break; } } return flow_out; } int max_flow() { int sum = 0; while (bfs()) { sum += dfs(s, inf); } return sum; } int main() { #ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF) { init(); for (int i = 1, u, v, flow;i <= m; i++) { scanf("%d%d%d", &u, &v, &flow); add(u, v, flow);add(v, u, 0); } printf("%d\n", max_flow()); } return 0; }
要解决这类问题,我们需要先了解什么是二分图?
二分图:一个图中的所有顶点可以分为两个集合 V,K ,其实两个集合内部的点彼此之间无边,如下图所示:(蓝色的点和红色的点分属于两个集合V,K)
然后我们回到这个题目上来,这个题目求的是最大可出战人数,实际上就是在二分图中找到两个集合中的最大匹配数,这类问题我们称之为二分图最大匹配数问题
属于网络流经典题目之一,下面说明一下建图的过程
1)由源点向集合V中每个点建一条容量为1的边
2)对于V,K集合之间存在的边e,v 为V中的点,k为K中的点,我们建一条容量为1的边,方向为 v --> k
3)由K中每个点向汇点建一条容量为1的边
当我们将图建好了后,我们求这个图的最大流,这个最大流即为二分图最大匹配数,下面展示一下建成的图:(S代表源点,T代表汇点,蓝色的边代表容量为1的边)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) cout<<"["<<x<<","<<y<<"] " //#define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e6 + 3; const int Max = 1e6 + 10; struct Edge { int to, next, flow; }edge[Max << 1];; int n, m, a, b, s, t; int head[Max], tot; int dis[Max]; int ans; bool vis[Max]; void init() { memset(head, -1, sizeof(head));tot = 0; ans = 0; } void add(int u, int v, int flow) { edge[tot].to = v; edge[tot].flow = flow; edge[tot].next = head[u]; head[u] = tot++; } bool bfs() { memset(dis, -1, sizeof(dis)); dis[s] = 0; queue<int>q; q.push(s); while (!q.empty()) { int u = q.front();q.pop(); for (int i = head[u]; i != -1;i = edge[i].next) { int v = edge[i].to; if (dis[v] == -1 && edge[i].flow > 0) { dis[v] = dis[u] + 1; if (v == t) return true; q.push(v); } } } return false; } int dfs(int u, int flow_in) { if (u == t) return flow_in; int flow_out = 0; for (int i = head[u]; i != -1;i = edge[i].next) { int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].flow > 0) { int flow_part = dfs(v, min(flow_in, edge[i].flow)); if (flow_part == 0) continue; flow_in -= flow_part; flow_out += flow_part; edge[i].flow -= flow_part; edge[i ^ 1].flow += flow_part; if (flow_in == 0)break; } } return flow_out; } int max_val() { int sum = 0; while (bfs()) { sum += dfs(s, inf); } return sum; } int main() { #ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif while (scanf("%d%d", &m, &n) != EOF) { init(); s = 0, t = n + 1; for (int i = 1;i <= m;i++) { add(s, i, 1);add(i, s, 0); //由源点向外籍飞行员建边 } for (int i = m + 1; i <= n;i++) { add(i, t, 1);add(t, i, 0); } while (scanf("%d%d", &a, &b) != EOF && a != -1 && b != -1) { add(a, b, 1);add(b, a, 0); } printf("%d\n", max_val()); for (int u = 1;u <= m;u++) { for (int i = head[u]; i != -1;i = edge[i].next) { if (edge[i].flow == 0 && edge[i].to != s && edge[i].to != t) { printf("%d %d\n", u, edge[i].to); } } } } return 0; }
这类题目相比于最大流问题新增了每天边单位流量的价格,问在最大流的情况下求出最小的费用。
这类题目和最大流很想,不过也有不小区别,对于这类问题,我们为每条边建的反边的价格是每天边的相反数,如图
然后我们的算法也不再是Dinic算法了,而是用spfa或者dijkstra
#pragma GCC optimize(2) #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) cout<<"["<<x<<","<<y<<"] " #define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e9 + 7; const int Max = 5e3 + 10; struct Edge { int to, rev; //rev记录反向边 int flow, cost;; }; int n, m, k; vector<Edge>edge[Max << 1]; int h[Max]; //每个结点的势 int dis[Max]; int pre_node[Max], pre_edge[Max]; //前驱结点和对应边 void add(int u, int v, int flow, int cost) { edge[u].push_back({ v,(int)edge[v].size(),flow,cost }); edge[v].push_back({ u,(int)edge[u].size() - 1,0,-cost }); } void min_cost_flow(int s, int t, int& min_cost, int& max_flow) { fill(h + 1, h + 1 + n, 0); min_cost = max_flow = 0; int tot = inf; //源点流量无限 while (tot > 0) { priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > >q; memset(dis, inf, sizeof(dis)); dis[s] = 0;q.push({ 0,s }); while (!q.empty()) { int u = q.top().second; int dist = q.top().first; q.pop(); if (dis[u] < dist)continue; //当前的距离不是最近距离 for (int i = 0;i < edge[u].size(); i++) { Edge &e = edge[u][i]; if (edge[u][i].flow > 0 && dis[e.to] > dis[u] + e.cost + h[u] - h[e.to]) { dis[e.to] = dis[u] +e.cost + h[u] - h[e.to]; pre_node[e.to] = u; pre_edge[e.to] = i; q.push({ dis[e.to],e.to }); } } } if (dis[t] == inf)break; //无法增广了,就是找到答案了 for (int i = 1;i <= n;i++) h[i] += dis[i]; int flow = tot; //求这一增广路径的流量 for (int i = t; i != s; i = pre_node[i]) flow = min(flow, edge[pre_node[i]][pre_edge[i]].flow); for (int i = t; i != s; i = pre_node[i]) { Edge& e = edge[pre_node[i]][pre_edge[i]]; e.flow -= flow; edge[i][e.rev].flow += flow; } tot -= flow; max_flow += flow; min_cost += flow * h[t]; } } int main() { #ifdef LOCAL //freopen("input.txt", "r", stdin); //freopen("output.txt", "w", stdout); #endif int s, t; while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF) { for (int i = 1, u, v, flow, cost;i <= m;i++) { scanf("%d%d%d%d", &u, &v, &flow, &cost); add(u, v, flow, cost); } int min_cost, max_flow; min_cost_flow(s, t, min_cost, max_flow); printf("%d %d\n", max_flow, min_cost); } return 0; }
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<queue> #include<string> #include<fstream> #include<vector> #include<stack> #include <map> #include <iomanip> #define bug cout << "**********" << endl #define show(x,y) cout<<"["<<x<<","<<y<<"] " //#define LOCAL = 1; using namespace std; typedef long long ll; const int inf = 0x3f3f3f3f; const ll mod = 1e9 + 7; const int Max = 1e5 + 10; struct Edge { int to, next; int flow, cost; }edge[Max << 1]; int n, m, s, t; int head[Max], tot; int dis[Max]; int pre[Max]; //记录增广路径此点的前一天边 bool vis[Max]; void init() { memset(head, -1, sizeof(head));tot = 0; } void add(int u, int v, int flow, int cost) { edge[tot].to = v; edge[tot].flow = flow; edge[tot].cost = cost; edge[tot].next = head[u]; head[u] = tot++; edge[tot].to = u; edge[tot].flow = 0; edge[tot].cost = -cost; edge[tot].next = head[v]; head[v] = tot++; } bool spfa(int s, int t) { memset(dis, inf, sizeof(dis)); memset(vis, 0, sizeof(vis)); memset(pre, -1, sizeof(pre)); queue<int>q; q.push(s);dis[s] = 0;vis[s] = true; while (!q.empty()) { int u = q.front();q.pop(); vis[u] = false; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (edge[i].flow > 0 && dis[v] > dis[u] + edge[i].cost) { dis[v] = dis[u] + edge[i].cost; pre[v] = i; if (!vis[v]) { vis[v] = true;q.push(v); } } } } return pre[t] != -1; } void min_cost_max_flow(int s, int t, int& max_flow, int& min_cost) { max_flow = 0; min_cost = 0; while (spfa(s, t)) { int flow = inf; for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]) //沿增广路回溯edge[i^1]即为其反边 { flow = min(flow, edge[i].flow); } for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to]) { edge[i].flow -= flow; edge[i ^ 1].flow += flow; min_cost += flow * edge[i].cost; } max_flow += flow; } } int main() { #ifdef LOCAL freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif while (scanf("%d%d%d%d", &n, &m, &s, &t) != EOF) { init(); for (int i = 1, u, v, flow, cost;i <= m;i++) { scanf("%d%d%d%d", &u, &v, &flow, &cost); add(u, v, flow, cost); } int max_flow = 0, min_cost = 0; min_cost_max_flow(s, t, max_flow, min_cost); printf("%d %d\n", max_flow, min_cost); } return 0; }
经典网络流题目模板(P3376 + P2756 + P3381 : 最大流 + 二分图匹配 + 最小费用最大流)
原文:https://www.cnblogs.com/winter-bamboo/p/11330082.html