简介
最小费用流问题就是一个最小费用流是指从带权图的起点到终点的路径,其权值之和最小。
具体做法
1.首先构造一个图,这个图的每一条边有一个容量c,有一个权值v。
2.再按照v来跑遍spfa求最短路径,并将这条最短路径记录下来。(设pre[i]=j表示在这条最短路径中,i是从j走过来的。每当可以更新最短路径时就可以同时更新pre啦~)
这个时候判断一下dis[t]是否为∞,如果是,则说明找不到增广路径,算法结束;否则说明找到增广路径,继续。
3.从汇点t开始往回倒,记录
min c(i,j)=m
,统计
Σv(i,j)
4.在从t开始,往回倒一次,这次
c(i,j)−=m,c(j,i)+=m
,然后转2.
由于本人弱,不会严格证明。
一个Code
这串代码类似于模板,
题目:n个盒子被放成一圈,每个盒子按顺时针编号为1到n,(1<=n<=1000)。每个盒子里都有一些球,且所有盒子里球的总数不超过n。
这些球要按如下的方式转移:每一步可以将一个球从盒子中取出,放入一个相邻的盒子中。目标是使所有的盒子中球的个数都不超过1。
题解
源点与每个盒子连一条容量为盒子中球的数量,费用为0的边;
每个盒子与汇点连一条容量为1,费用为0的边;
每个盒子与左边的盒子连一条边,与右边的盒子连一条边,这些边都是容量为∞,费用为1的边。
然后跑一遍最小费用流即可。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define N 1005
#define M 10003
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define edge(i,x) for(i=head[x];i;i=next[i])
using namespace std;
int tot,i,j,k,l,n,ans;
int go[M*2],next[M*2],head[N],flow[M*2],val[M*2];
int pre[N],dis[N],qu[M];
int a[N];
bool bz[N];
void lb(int x,int y,int z,int w)
{
go[++tot]=y;
next[tot]=head[x];
head[x]=tot;
flow[tot]=z;
val[tot]=w;
go[++tot]=x;
next[tot]=head[y];
head[y]=tot;
flow[tot]=0;
val[tot]=-w;
}
bool spfa()
{
memset(dis,127,sizeof(dis));dis[0]=0;
memset(bz,0,sizeof(bz));
memset(pre,0,sizeof(pre));
int t=0,w=1,i,j,now,to;
qu[1]=0;
while (t<w)
{
now=qu[++t];
edge(i,now)
{
to=go[i];
if (flow[i]>0 && dis[to]>dis[now]+val[i])
{
dis[to]=dis[now]+val[i];
pre[to]=i;
if (!bz[to])
{
bz[to]=1;
qu[++w]=to;
}
}
}
bz[now]=0;
}
if (dis[n+1]!=2139062143) return 1;else return 0;
}
void find()
{
int x=n+1,sum=0,minc=2147483647;
while (x)
{
minc=min(minc,flow[pre[x]]);
x=go[pre[x]^1];
}
x=n+1;
while (x)
{
flow[pre[x]]-=minc;
flow[pre[x]^1]+=minc;
sum+=val[pre[x]];
x=go[pre[x]^1];
}
ans+=sum*minc;
}
int main()
{
scanf("%d",&n);
tot=1;
fo(i,1,n)
{
scanf("%d",&a[i]);
lb(0,i,a[i],0);
lb(i,n+1,1,0);
if (i>1) lb(i,i-1,2147483647,1);
if (i<n) lb(i,i+1,2147483647,1);
}
lb(1,n,2147483647,1);
lb(n,1,2147483647,1);
while (spfa()) find();
printf("%d",ans);
}