一篇很好的网络流博客:https://www.cnblogs.com/rmy020718/p/9546071.html
一篇很好的最小费用最大流博客:https://blog.csdn.net/lym940928/article/details/90209172
SPFA(可以判断负权回路)
反向边的费用要设置为负。
为什么会用到反向边呢?因为每次走的时候不能确定所走的路径就是最佳路径,所以需要后悔药——反向边,这样的话,即使走错了也可以通过走反向边纠正。如图,如果一开始走了黄色路径,那答案肯定错了,如果有了反向边,就可以通过蓝色路径纠正错误,走完蓝色路径之后,图中已经没有可走的正向边,程序结束。
也可以看出来,只有这种情况的边会用到反向边,而且反向边必定走两遍(如果只走一遍说明路径唯一,则不需要反向边),那么就可以采用把正向边费用存为正,反向边费用为负的方法将多算的费用抵消
#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <cstring>
#include <stdio.h>
#include <queue>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <string>
#define MAX 50500
#define INF 0x3f3f3f3f
#define EXP 1e-9
using namespace std;
typedef long long ll;
int n,m,s,t;
struct node{
int nxt,to,w,f;
}e[MAX<<1];
int head[5050];
int tot;
ll cnt,ans;
int dis[5050];
int vis[5050];
int pre[5050]; //到达某点的最短边
queue<int >q;
inline void adde(int a,int b,int c,int d){
e[tot]=(node){head[a],b,c,d};
head[a]=tot++;
e[tot]=(node){head[b],a,0,-d};
head[b]=tot++;
}
inline bool SPFA(){
memset(dis,0x3f,sizeof(dis));
while(!q.empty())
q.pop();
dis[s]=0;
q.push(s);
vis[s]=1;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i!=-1;i=e[i].nxt){
int y=e[i].to;
if(e[i].w>0&&dis[y]>dis[x]+e[i].f){
dis[y]=dis[x]+e[i].f;
pre[y]=i;
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
//printf("%d+++\n",dis[t]);
if(dis[t]==INF)return 0;
int flow=INF;
for(int i=t;i!=s;i=e[pre[i]^1].to)
flow=min(flow,e[pre[i]].w);
cnt+=flow;
ans+=flow*dis[t];
for(int i=t;i!=s;i=e[pre[i]^1].to){
e[pre[i]].w-=flow;
e[pre[i]^1].w+=flow;
}
return 1;
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
ans=cnt=tot=0;
int a,b,c,d;
for(int i=0;i<m;i++){
scanf("%d%d%d%d",&a,&b,&c,&d);
adde(a,b,c,d);
}
while(SPFA());
printf("%lld %lld\n",cnt,ans);
return 0;
}