目录
在洛谷题解中看到了一句很有启发的话:网络流善于解决各种有要求的匹配
联想到题目是匹配问题,且满足网络流要求的数据范围,可以尝试网络流
V是点的数目,E是边的数目
最大流流算法
(EK算法)
时间复杂度 O(V*(E^2))
定义:
我们有一个 源点(只有流出量) 和 汇点(只有流入量)
每条边有一个 流量 和 容量,流量最初为0,容量是题目告知我们的,我们要求的是 在所有边的 流量 <= 容量 && 每个点流入量 = 流出量 时,流入汇点的最大流是多少。
首先我们需要知道残留网络:
残留网络就是反向图的流量,我们正向遍历一个图,会走一条路径到汇点。
一条路径的 增广路 = min(经过的每条边的 {容量 - 当前流量} )
但是我们这条边可能是反向的流,所以
- 正向边的容量 -= 增广路
- 反向边容量 += 增广路
找到增广路,我们就 flow += 增广路
当找不到增广路就结束了这个算法。
因为看了vector建图和链式前向星建图的效率和空间差异,就手动改了一下模板,不用常用的mp[][] 数组标记,练习一下链式前向星。不太懂的,这里看看https://blog.csdn.net/castomere/article/details/80426485
最大流最小割定理:
割 是 一些边被砍断,让S(源点) 和 T(汇点)不连通的边,这些边的容量sum就是 割 的容量,最小割,就是让割的容量最小,想像最大流时,一些边被流填满,那么 最大流 就是 最小割的容量
洛谷3376
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;
// O{V * E ^ 2}
/*
源点 st , 汇点 ed
*/
int N , M;
int st , ed;
int cnt;
struct edge{
int Next;//下一条边的存储下标
int to;//这条边的终点
int w;//边权
};
edge Edge[MaxN];
int head[Maxn];//以 i 为起点的第一条边的标记
void AddEdge(int u , int v , int w){
Edge[++cnt].Next = head[u];
Edge[cnt].w = w;
Edge[cnt].to = v;
head[u] = cnt;
}
int pre[Maxn][2];
int Flow;
int flow[Maxn];
queue <int> que;
bool Bfs(){
while(!que.empty()) que.pop();
for(int i = 0 ; i <= N ; i++) pre[i][0] = pre[i][1] = -1;
flow[st] = Inf;
int q;
que.push(st);
while(!que.empty()){
q = que.front(); que.pop();
if(q == ed) break;
for(int i = head[q] ; ~i ; i = Edge[i].Next){
int nq = Edge[i].to;
if(nq != st && Edge[i].w && pre[nq][0] == -1){//下个点nq不是st,且边权>0,nq没被遍历过
pre[nq][0] = i;//记录前一条边的编号
pre[nq][1] = q;//记录nq前一个点q
flow[nq] = min(Edge[i].w , flow[q]);//到nq路径上取min边权
que.push(nq);
}
}
}
if(pre[ed][0] == -1) return false;
return true;
}
void MaxFlow(){
int add , pl , fa , p;
while(Bfs()){
add = flow[ed];
p = ed;
while(p != st){
pl = pre[p][0];//p上一条边的编号
p = pre[p][1];//p变为它的上一个点
Edge[pl].w -= add;
Edge[pl^1].w += add;
}
Flow += add;
}
printf("%d\n",Flow);
return;
}
void init(){
for(int i = 0 ; i <= N ; i++) head[i] = -1;
cnt = -1;//从-1 开始 可以用 x ^ 1 来关联正反边
return;
}
int main()
{
scanf(" %d %d %d %d",&N,&M,&st,&ed);
init();
for(int i = 1 ; i <= M ; i++){
int u , v , c , ct1 , ct2; scanf(" %d %d %d",&u,&v,&c);
AddEdge(u , v , c);
AddEdge(v , u , 0);
}
MaxFlow();
return 0;
}
Dinic算法
EK通常解决10^3 –10^4规模的网络
而dinic能解决10^4–10^5的网络
时间复杂度O((V ^ 2) * E)
(真的快了不是一点,这一道题,Dinic是EK的 1/4 时间)
算法流程:
- 在存在增广路的边上建立分层图,如果汇点不在存在增广路的分层图上,那么就不存在新的增广路,结束算法
- 当存在汇点增广路,执行Dfs操作,在分层图中寻找增广路,增广路径从 u 到 v , 当且仅当dep[v] = dep[u] + 1
- 找到增广路,Dfs回溯时减去增广路,一个节点可能减了多次增广路。
洛谷3376
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;
int N , M;
int st , ed;
int Flow;
int cnt;
struct edge{
int Next;
int to;
int w;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w){
Edge[++cnt].Next = head[u];
Edge[cnt].w = w;
Edge[cnt].to = v;
head[u] = cnt;
}
int dep[Maxn];
queue <int> que;
bool Bfs(){
for(int i = 1 ; i <= N ; ++i) dep[i] = 0;
while(!que.empty()) que.pop();
dep[st] = 1;
que.push(st);
int q , v;
while(!que.empty()){
q = que.front(); que.pop();
for(int i = head[q] ; ~i ; i = Edge[i].Next){
v = Edge[i].to;
if(Edge[i].w && !dep[v]){
que.push(v);
dep[v] = dep[q] + 1;
if(v == ed) return true;
}
}
}
return false;
}
int Dfs(int x , int flow){
if(x == ed) return flow;
int res = flow , add;
for(int i = head[x] ; ~i && res ; i = Edge[i].Next){
int v = Edge[i].to;
if(Edge[i].w && dep[v] == dep[x] + 1){
add = Dfs(v , min(res , Edge[i].w));
if(!add) dep[v] = 0;
Edge[i].w -= add;
Edge[i^1].w += add;
res -= add;
}
}
return flow - res;
}
void Dinic(){
int flow;
while(Bfs()){
while(flow = Dfs(st , Inf)){
Flow += flow;
}
}
printf("%d\n",Flow);
}
void init(){
for(int i = 1 ; i <= N ; i++) head[i] = -1;
Flow = 0;
cnt = -1;
}
int main()
{
scanf(" %d %d %d %d",&N,&M,&st,&ed);
init();
for(int i = 1 ; i <= M ; i++){
int u , v , c; scanf(" %d %d %d",&u,&v,&c);
AddEdge(u , v , c);
AddEdge(v , u , 0);
}
Dinic();
}
洛谷 P2763
我们已经学会了基础的最大流,那么就需要继续研究最小费用最大流 和 最大费用最大流
最小费用最大流
定义:
在最大流相同的情况下,我们要求花费最小的费用。
我们发现它类似最短路的条件,我们把价格所以就在求解增广路的过程中加一个 条件来筛选,即选择最短路进行增广。
分析:
因为dijkstra是在一个点的连边中 选最小的边权进行拓展,就像它不能处理带负权的最短路一样,所以我们用SPFA维护 反向边的负权流量。其他的同步最大流算法。
洛谷P3381
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;
int N , M;
int st , ed;
int Flow , MinCost;
int cnt;
struct edge{
int Next;
int to;
int w;
int c;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w , int cost){
Edge[++cnt].Next = head[u];
Edge[cnt].w = w;
Edge[cnt].to = v;
Edge[cnt].c = cost;
head[u] = cnt;
}
int dis[Maxn] , pre[Maxn] , last[Maxn] , flow[Maxn];
bool vis[Maxn];
//last[i] 前一条边 , pre[i] 前一个点
queue <int> que;
bool Spfa(){
for(int i = 1 ; i <= N ; ++i){
dis[i] = flow[i] = Inf;
vis[i] = false;
}
int q , v , w , cost;
pre[ed] = -1;
vis[st] = 1; dis[st] = 0;
que.push(st);
while(!que.empty()){
q = que.front(); que.pop();
vis[q] = 0;
for(int i = head[q] ; ~i ; i = Edge[i].Next){
v = Edge[i].to;
w = Edge[i].w;
cost = Edge[i].c;
if(w > 0 && dis[v] > dis[q] + cost){
dis[v] = dis[q] + cost;
last[v] = i;
pre[v] = q;
flow[v] = min(flow[q] , w);
if(!vis[v]){
vis[v] = 1;
que.push(v);
}
}
}
}
if(pre[ed] != -1) return true;
return false;
}
void MinCostFlow(){
while(Spfa()){
int p = ed;
Flow += flow[ed];
MinCost += flow[ed] * dis[ed];
while(p != st){
Edge[last[p]].w -= flow[ed];
Edge[last[p]^1].w += flow[ed];
p = pre[p];
}
}
printf("%d %d\n",Flow , MinCost);
}
void init(){
for(int i = 0 ; i <= N ; i++){
head[i] = -1;
}
cnt = -1;
Flow = MinCost = 0;
}
int main()
{
scanf(" %d %d %d %d",&N ,&M ,&st ,&ed);
init();
for(int i = 1 ; i <= M ; i++){
int u , v , w , cost; scanf(" %d %d %d %d",&u,&v,&w,&cost);
AddEdge(u , v , w , cost);
AddEdge(v , u , 0 , -cost);
}
MinCostFlow();
}
最大费用最大流
先贴个板子
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;
int N , M;
int st , ed;
int Flow , MaxCost;
int cnt;
struct edge{
int pre;
int Next;
int to;
int w;
int c;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w , int cost){
Edge[++cnt].Next = head[u];
Edge[cnt].pre = u;
Edge[cnt].w = w;
Edge[cnt].to = v;
Edge[cnt].c = cost;
head[u] = cnt;
}
int dis[Maxn] , preWay[Maxn] , flow[Maxn];
bool vis[Maxn];
//last[i] 前一条边 , pre[i] 前一个点
queue <int> que;
bool Spfa(){
for(int i = 1 ; i <= N ; ++i){
dis[i] = -Inf;
preWay[i] = -1;
vis[i] = false;
}
int q , v , w , cost;
vis[st] = 1; dis[st] = 0;
que.push(st);
while(!que.empty()){
q = que.front(); que.pop();
vis[q] = 0;
for(int i = head[q] ; ~i ; i = Edge[i].Next){
v = Edge[i].to;
w = Edge[i].w;
cost = Edge[i].c;
if(w > 0 && dis[v] < dis[q] + cost){
dis[v] = dis[q] + cost;
preWay[v] = i;
if(!vis[v]){
vis[v] = 1;
que.push(v);
}
}
}
}
if(preWay[ed] != -1) return true;
return false;
}
void MaxCostFlow(){
while(Spfa()){
int tmpFlow = Inf;
for(int i = preWay[ed] ; ~i ; i = preWay[Edge[i].pre] ){
if(tmpFlow > Edge[i].w){
tmpFlow = Edge[i].w;
}
}
for(int i = preWay[ed] ; ~i ; i = preWay[Edge[i].pre]){
Edge[i].w -= tmpFlow;
Edge[i^1].w += tmpFlow;
MaxCost += tmpFlow * Edge[i].c;
}
Flow += tmpFlow;
}
printf("%d %d\n",Flow , MaxCost);
}
void init(){
for(int i = 0 ; i <= N ; i++){
head[i] = -1;
}
cnt = -1;
Flow = MaxCost = 0;
}
int main()
{
scanf(" %d %d %d %d",&N ,&M ,&st ,&ed);
init();
for(int i = 1 ; i <= M ; i++){
int u , v , w , cost; scanf(" %d %d %d %d",&u,&v,&w,&cost);
AddEdge(u , v , w , cost);
AddEdge(v , u , 0 , -cost);
}
MaxCostFlow();
}