即一个带边权的网络流,给定一个流图以及每条边单位流的费用,求最大流情况下的最小费用。
这里因为带边权,所以之前的贴标签、分层算法均失去作用,只能用最普通的EK算法,即不断BFS找增广路,在此过程中,把费用看作边的长度(反向边为其相反数,保证增广过程可逆),一边找增广路,一边做spfa(带负权边只能用spfa)找最短路,这样找到的增广路一定是花费最少的。
代码如下:
#include<iostream> #include<iomanip> #include<ctime> #include<climits> #include<algorithm> #include<queue> #include<vector> #include<cstring> #include<cstdio> #include<cstdlib> #include<map> using namespace std; typedef unsigned long long LL; #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,a,b) for(int i=a;i>=b;i--) inline int read(){ int x=0;char ch=getchar(); while(ch<'0'||ch>'9'){ ch=getchar(); } while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } return x; } const int MAXN=5001,MAXM=100001; int head[MAXN],pre[MAXN],f[MAXN],e[MAXN],dis[MAXN],step[MAXN]; int flow[MAXM],to[MAXM],next[MAXM],cost[MAXM],cnt=0,n,m,s,t; int Insert(int u,int v,int w,int c){//链式前向星加边 to[cnt]=v; next[cnt]=head[u]; cost[cnt]=c; flow[cnt]=w; head[u]=cnt++; to[cnt]=u; next[cnt]=head[v]; cost[cnt]=-c; flow[cnt]=0; head[v]=cnt++; } int spfa(){ memset(dis,127,sizeof(dis));//赋最大值 dis[s]=0; queue<int>q; memset(f,0,sizeof(f)); q.push(s);f[s]=1;e[s]=INT_MAX;e[t]=0; while(!q.empty()){//SPFA int u=q.front();q.pop();f[u]=0; for(int i=head[u];i!=-1;i=next[i]){ if(flow[i]&&dis[u]+cost[i]<dis[to[i]]){ int p=to[i]; e[p]=min(flow[i],e[u]); dis[p]=dis[u]+cost[i]; pre[p]=u; step[p]=i;//记录连接节点的边的序号,在增广时方便查找 if(!f[p]){ q.push(p); f[p]=1; } } } } return e[t]; } int COST=0,FLOW=0; void MIN_COST(){ int p; while(p=spfa()){ int now=t; while(now!=s){ flow[step[now]]-=p; flow[step[now]^1]+=p; now=pre[now]; } COST+=dis[t]*p;//一次增广的花费为最短路径长度*增广流量 FLOW+=p; } } int main(){ n=read();m=read();s=read();t=read(); memset(head,-1,sizeof(head)); rep(i,1,m){ int u=read(),v=read(),w=read(),c=read(); Insert(u,v,w,c); } MIN_COST(); printf("%d %d",FLOW,COST); return 0; }