最大流板子
如 果 你 会 最 大 流 , 那 这 个 基 本 上 可 以 秒 懂 。 如果你会最大流,那这个基本上可以秒懂。 如果你会最大流,那这个基本上可以秒懂。
问 题 描 述 \color{Red}问题描述 问题描述
给 出 一 个 网 络 图 , 以 及 其 源 点 s 和 汇 点 t 给出一个网络图,以及其源点s和汇点t 给出一个网络图,以及其源点s和汇点t
每 条 边 已 知 其 最 大 流 量 和 单 位 流 量 费 用 每条边已知其最大流量和单位流量费用 每条边已知其最大流量和单位流量费用
求 出 其 网 络 最 大 流 和 在 最 大 流 情 况 下 的 最 小 费 用 。 求出其网络最大流和在最大流情况下的最小费用。 求出其网络最大流和在最大流情况下的最小费用。
P3381 【模板】最小费用最大流
可以发现相比最大流多了一个单位流量费用的限制
那么原来利用bfs找路径的思想就不可行了,因为找的路径代价可能很大
那 找 怎 样 的 路 径 比 较 合 理 ? \color{Orange}那找怎样的路径比较合理? 那找怎样的路径比较合理?
把 单 位 费 用 看 作 边 的 权 值 , 从 源 点 到 汇 点 跑 最 短 路 把单位费用看作边的权值,从源点到汇点跑最短路 把单位费用看作边的权值,从源点到汇点跑最短路
每 次 找 一 条 s 到 t 的 花 费 最 小 的 路 径 , 同 时 记 录 路 径 和 路 上 的 最 小 流 量 每次找一条s到t的花费最小的路径,同时记录路径和路上的最小流量 每次找一条s到t的花费最小的路径,同时记录路径和路上的最小流量
如 果 汇 点 的 最 短 路 被 更 新 , 说 明 找 到 路 径 , 完 成 了 b f s 判 断 是 否 有 路 径 的 功 能 如果汇点的最短路被更新,说明找到路径,完成了bfs判断是否有路径的功能 如果汇点的最短路被更新,说明找到路径,完成了bfs判断是否有路径的功能
而且 通 过 记 录 走 到 汇 点 的 路 径 , 可 以 直 接 对 路 径 上 边 的 流 量 做 修 改 通过记录走到汇点的路径,可以直接对路径上边的流量做修改 通过记录走到汇点的路径,可以直接对路径上边的流量做修改
如 此 一 来 , 不 停 的 利 用 s p f a 找 最 短 路 即 可 如此一来,不停的利用spfa找最短路即可 如此一来,不停的利用spfa找最短路即可
最 小 费 用 最 大 流 ( s p f a 版 本 ) 就 是 这 样 一 种 算 法 最小费用最大流(spfa版本)就是这样一种算法 最小费用最大流(spfa版本)就是这样一种算法
#include
using namespace std;
const int maxn=50009;
const int inf=1e9;
int n,m,s,t;
int maxflow,mincost;
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct EDGE{
int to,nxt,w,flow;//分别代表
}d[maxn<<1];
void add(int u,int v,int flow,int w)//flow最大流量,w单位费用
{
d[++cnt].to=v,d[cnt].flow=flow,d[cnt].w=w,d[cnt].nxt=head[u],head[u]=cnt;
}
bool spfa()
{
queueq;
for(int i=0;i<=n;i++) dis[i]=inf;
memset(vis,0,sizeof(vis));
q.push(s);
dis[s]=0,vis[s]=1;
incf[s] = inf;//初始流量无限大
while( !q.empty() )
{
int u=q.front(); q.pop();
vis[u]=0;//出队
for(int i=head[u];i;i=d[i].nxt)
{
if( !d[i].flow ) continue;//无流量了
int v=d[i].to;
if( dis[v]>dis[u]+d[i].w )
{
dis[v]=dis[u]+d[i].w;
incf[v] = min(incf[u],d[i].flow);//更新当前流量
pre[v]=i;//记录从哪条边过来的
if( !vis[v] ) vis[v]=1,q.push(v);
}
}
}
if( dis[t]==inf ) return 0;
return 1;
}
void dinic()
{
while( spfa() )
{
int x=t;//倒回去找路径
maxflow+=incf[t];
mincost+=dis[t]*incf[t];
int i;
while(x != s)
{
i=pre[x];
d[i].flow-=incf[t];//减去流量
d[i^1].flow+=incf[t];//加上流量
x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去
}
}
}
int main()
{
cin >> n >> m >> s >> t;
for(int i=1;i<=m;i++)
{
int l,r,w,x;
cin >> l >> r >> w >> x;
add(l,r,w,x); add(r,l,0,-x);
}
dinic();
cout << maxflow << " " << mincost;
}
下面是最大费用最大流,就是建边的时候把边权取反就行,输出再次取反
#include
using namespace std;
const int maxn=50009;
const int inf=1e9;
int n,m,s,t,a[59][59];
int maxflow,mincost;
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct EDGE{
int to,nxt,w,flow;//分别代表
}d[maxn<<1];
void add(int u,int v,int flow,int w)//flow最大流量,w单位费用
{
d[++cnt].to=v,d[cnt].flow=flow,d[cnt].w=w,d[cnt].nxt=head[u],head[u]=cnt;
d[++cnt].to=u,d[cnt].flow=0,d[cnt].w=-w,d[cnt].nxt=head[v],head[v]=cnt;
}
bool spfa()
{
queue<int>q;
for(int i=0;i<=t;i++) dis[i]=inf;
memset(vis,0,sizeof(vis));
q.push(s);
dis[s]=0,vis[s]=1;
incf[s] = inf;//初始流量无限大
while( !q.empty() )
{
int u=q.front(); q.pop();
vis[u]=0;//出队
for(int i=head[u];i;i=d[i].nxt)
{
if( !d[i].flow ) continue;//无流量了
int v=d[i].to;
if( dis[v]>dis[u]+d[i].w )
{
dis[v]=dis[u]+d[i].w;
incf[v] = min(incf[u],d[i].flow);//更新当前流量
pre[v]=i;//记录从哪条边过来的
if( !vis[v] ) vis[v]=1,q.push(v);
}
}
}
if( dis[t]==inf ) return 0;
return 1;
}
void dinic()
{
while( spfa() )
{
int x=t;//倒回去找路径
maxflow+=incf[t];
mincost+=dis[t]*incf[t];
int i;
while(x != s)
{
i=pre[x];
d[i].flow-=incf[t];//减去流量
d[i^1].flow+=incf[t];//加上流量
x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去
}
}
}
int main()
{
cin >> n >> m;
s=0,t=n*n*2+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) cin >> a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
int x=(i-1)*n+j,y=i*n+j;
if( i!=n ) add(x+n*n,y,inf,0);
if( j!=n ) add(x+n*n,x+1,inf,0);
add(x,x+n*n,1,-a[i][j]);
add(x,x+n*n,inf,0);
}
add(s,1,m,0);
add(n*n*2,t,m,0);
dinic();
cout << -mincost;
}