费用流
费用流:
定义:
给定一个网络 , 每条边除了有容量限制 , 还有一个单位流量的费用
当 的流量 时,需要花费
也满足对称性,即
则该网络中总花费最小的最大流称为 最小费用最大流。
即在最大化 的前提下最小化
分析:
可以使用 算法。
算法是一个贪心的算法,它的思路是每次寻找单位费用最小的增广路进行增广,直到图上不存在增广路为止。
如果图上存在单位费用为负的圈, 算法无法算出,需要用消圈算法消去负环。
使用:
只需要将 算法或 算法中寻找增广路的过程,替换为用最短路算法寻找单位费用最小的增广路即可。
时间复杂度为 ,即为 增广路的长度
过程:
- 初始化:
数组(从源点 到各点的最小费用)全部赋值为 ,用一个队列保存所有待松弛的顶点,初始时将 点放入队列中。 - 队列 松弛操作:
每次出队一个顶点 ,对其所有的边进行松弛,如果存在某条边 松弛成功 ,则将 加入队列中(当 不在队列时).
重复以上操作直到队列为空或者发现负权环,如果网络中存在负权回路,则算法永远都不会结束,陷入死循环。
判断是否存在负权环的方法:
对任何一个顶点,每进入一次队列,意味着需要进行一次松弛,即如果某个顶点进入队列的次数超过 ,说明存在负权环,因为所有点一定都遍历过一遍了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int inf=0x3f3f3f3f,N=5005,M=200005;
int nxt[M],ver[M],tot=1,edge[M],head[N],w[M];
int n,m,s,t;
int maxflow,mincost;
int dis[N],pre[N],cur[N],last[N],flow[N];
bool vis[N];
queue<int> q;
void add(int x,int y,int z,int f){
ver[++tot]=y; edge[tot]=z; nxt[tot]=head[x]; head[x]=tot; w[tot]=f;
ver[++tot]=x; edge[tot]=0; nxt[tot]=head[y]; head[y]=tot; w[tot]=-f;
}
bool spfa(int s,int t){
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
q.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;
while(!q.empty()){
int x=q.front(); q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i],z=edge[i],W=w[i];
if(!z||dis[y]<=dis[x]+W) continue;//最短路算法
dis[y]=dis[x]+W;
pre[y]=x;//记录上一个点
last[y]=i;//记录上一条边
flow[y]=min(flow[x],z);
if(!vis[y]){vis[y]=1; q.push(y);}
}
}
return pre[t]!=-1;
}
void MCMF(){
while(spfa(s,t)){
int x=t;
maxflow+=flow[x]; mincost+=flow[x]*dis[x];
while(x!=s){
edge[last[x]]-=flow[t];
edge[last[x]^1]+=flow[t];
x=pre[x];
}
}
}
int main(){
cin>>n>>m>>s>>t;
for(int i=1,x,y,z,f;i<=m;i++){
scanf("%d%d%d%d",&x,&y,&z,&f); add(x,y,z,f);
}
MCMF();
cout<<maxflow<<" "<<mincost<<endl;
system("pause");
return 0;
}
进一步分析:
网络流题目不可能像这样这么简单,如果碰到有请求的点,我们就需要判断是否需要按照点或者请求建边了。
给一道例题:
题意:
飞机飞来飞去,问获得的收益最大值(开摆!)
分析:
这题就是明显的按照请求建边,因为按照点建边,需要按时间分层。
而按照请求建边,时间则可以处理掉。
一开始 , 为虚假的源点
源点:
先判断从 可不可以直接到达请求的起点,建边流量 , 费用
if(q[i].s>=times[1][q[i].a]) //能从起点飞到a点
add(t-1,i,inf,f[1][q[i].a]);//
但是又有 架飞机这个条件,不知道 会去哪一条路,所以就再建一个真正的源点,向先前的源点(表示基地)建一条流量 ,费用 的边.
汇点:
判断请求结束后能不能在规定时间内到达基地点 ,然后建边,流量 , 费用
if(q[i].t+times[q[i].b][1]<=T) //如果能够直接回来,就建这条边
add(i+m,t,inf,f[q[i].b][1]);
请求之间转移:
能够不回去基地,就不回去, 枚举每个请求能否满足,如果满足,连边 流量 ,边权
for(int j=1;j<=m;j++)
if(q[i].t+times[q[i].b][q[j].a]<=q[j].s)//判断前一个结束后有时间到达后一个的开始时间
add(i+m,j,inf,f[q[i].b][q[j].a]);
``
然后就可以快乐的 $Dinic$ 了!
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=2e5+5,M=205,inf=0x3f3f3f3f;
int n,m,K,T,s,t;
int edge[N],ver[N],head[N],nxt[N],tot=1,w[N];
int pre[N],flow[N],dis[N],last[N];
ll mincost,maxflow;
bool vis[N];
queue<int> Q;
int times[M][M],f[M][M];
struct node{
int a,b,s,t,c;
}q[N];
void add(int x,int y,int z,int f){
ver[++tot]=y; edge[tot]=z; w[tot]=f; nxt[tot]=head[x]; head[x]=tot;
ver[++tot]=x; edge[tot]=0; w[tot]=-f; nxt[tot]=head[y]; head[y]=tot;
}
bool spfa(int s,int t){
memset(dis,0x7f,sizeof(dis));
memset(flow,0x7f,sizeof(flow));
memset(vis,0,sizeof(vis));
Q.push(s); vis[s]=1; dis[s]=0; pre[t]=-1;
while(!Q.empty()){
int x=Q.front(); Q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i],z=edge[i],W=w[i];
if(!z||dis[y]<=dis[x]+W) continue;
dis[y]=dis[x]+W;
pre[y]=x;
last[y]=i;//记录上一条边
flow[y]=min(flow[x],z);
if(!vis[y]){vis[y]=1; Q.push(y);}
}
}
return pre[t]!=-1;
}
void MCMF(){
while(spfa(s,t)){
int x=t;
maxflow+=flow[x]; mincost+=flow[x]*dis[x];
while(x!=s){
edge[last[x]]-=flow[t];
edge[last[x]^1]+=flow[t];
x=pre[x];
}
}
}
int main(){
cin>>n>>m>>K>>T;
s=0,t=2*m+2;//虚假的源点是 t-1
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",×[i][j]);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&f[i][j]);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d%d",&q[i].a,&q[i].b,&q[i].s,&q[i].t,&q[i].c);
q[i].a++; q[i].b++;
}
for(int i=1;i<=m;i++){//按照请求建边
add(i,i+m,1,-q[i].c);//按照负边权建边
if(q[i].t+times[q[i].b][1]<=T) //如果能够直接回来,就建这条边
add(i+m,t,inf,f[q[i].b][1]);
else continue;//不能回家
if(q[i].s>=times[1][q[i].a]) //能从起点飞到a点
add(t-1,i,inf,f[1][q[i].a]);//
for(int j=1;j<=m;j++)
if(q[i].t+times[q[i].b][q[j].a]<=q[j].s)//判断前一个结束后有时间到达后一个的开始时间
add(i+m,j,inf,f[q[i].b][q[j].a]);
}
add(s,t-1,K,0);//从源点到汇点控制k架飞机
MCMF();
cout<<-mincost<<endl;//边权取反,答案也要取反
system("pause");
return 0;
}
本文作者:Evitagen
本文链接:https://www.cnblogs.com/guanlexiangfan/p/15391676.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步