自己再稍总结一下
先定义dis[i],
也是类似SPFA做法的最短路距离定义,但注意是倒着的,即不是有S出去,而是到S回来. 具体来说,意义大致变成其所在所有增广路末尾到该点的距离的最小值.
所以,根据最短路的性质有 (1)
对任一条边(u,v)都有dis[u]<=dis[v]+w(u,v)
(2) 最短路上的边(u,v)必有 dis[u]=dis[v]+w(u,v)
会发现上面那个不等式,有点类似KM,然后就想到这样照着KM做:
增广的时候 只有当 边(u,v) 满足
dis[v]+w(u,v)=dis[u]才去从源点增广. 如果发现增广不到汇点,则修改dis值.
修改dis值,即是将所有在增广路上的点u的dis加上一个delt,而delt=min(dis[v]+w(u,v)-dis[u])
其中u在增广路上,v不在. 和KM 一样,这样至少会多使一条边满足(2)可增广,且不会破坏(1).
这个delt可以和KM里面一样,在增广的时候顺便求slk[].
复杂度降成 O(|V|)
其做法本质是看到了SPFA做最短路时,做了很多无用功,即也会去更新绕很远实质不可能被用到的一些顶点.
然后ZKW算法,就利用最短路上必有dis[v]+w(u,v)=dis[u]
的特点少去尝试了很多无用的顶点.
但是在稀疏图中,最坏情况下会在茫茫中只增加区区一边...重要的是本身增加了所有边后,也还是要继续增广的..于是开头就已经很慢了...
所以ZKW费用流一般只适用于 稠密图(如KM适用的二分图什么的,当然范围比KM广很多,有点像拓展)...
例题是 [BZOJ 1070][SCOI 2007]修车
.
设time[i][j]表示第i部车被第j个修理人员修的时间.
这道题看到数据范围+YY还是很容易想到费用流的.
但是怎么构图呢..?
会发现,瓶颈在于,要考虑等待别的车的时间以及自己修车的时间,这些都是各自不同的..很麻烦.
如何单一化?可以想到只考虑自己对别人的影响. (有点逆向的味道?!)
然后这个显然只跟自己是那个修理人员的倒数第几个修的有关.
数据很小,想到可以暴力拆点,把每个维修人员拆成n个点,表示是倒数第几个修的.
再建n个点表示n部车.
那么如果第i部车,是在第j个维修人员那里作为倒数第z个修,那么就像i->(j,z)连一条费用(影响)是
time[i][j]*z (要加上自己的哦.),容量为1的边.
再向源点到n部车的点连一条容量为1费用为0的边,表示每部车只有一辆.
每个表示第j个维修人员,倒数第z个修的点(j,z)向汇点连一条容量为1费用为0的边,表示第j个维修人员,倒数第z个修的车,只能有一辆.
答案即为满足最大流(所有车都有的修的)前提下的,最小费用流.
但显然这里最大流是必然满足的,不必判断.
CODE:
#include<
iostream>
#include< cstdio>
#include< cstdlib>
#include< cstring>
using namespace std;
const int
maxn=800,maxm=500000,INF=1< <
30;
int
tot,ans,n,m,S,T,V[maxm],G[maxm],C[maxm],N[maxm],F[maxn],B[maxm],v[maxn],slk[maxn],d[maxn];
int tt[80][80];
int get(int a,int b){ return
(a-1)*n+b+n; }
void dw(int &a,int b){ if (b< a)
a=b; }
void add(int a,int b,int up,int
co)
{
++tot;V[tot]=b;G[tot]=up;C[tot]=co;N[tot]=F[a];F[a]=tot;
++tot;V[tot]=a;G[tot]=0;C[tot]=-co;N[tot]=F[b];F[b]=tot;
B[tot]=tot-1;B[tot-1]=tot;
}
int aug(int u,int f)
{
int p,t,left=f;
if (u==T) { ans+=f*d[S];return
f; }
v[u]=1;
for (p=F[u];p;p=N[p])
if
(G[p]>0&&!v[V[p]])
{
t=d[V[p]]+C[p]-d[u];
if (t==0)
{
int delt=aug(V[p],G[p]< left? G[p] : left);
if (delt>0)
G[p]-=delt,G[B[p]]+=delt,left-=delt;
if (left==0) return f;
}else dw(slk[V[p]],t);
}
return f-left;
}
bool modlabel()
{
int
delt=INF,i;
for
(i=1;i<=T;i++)
if (!v[i]) { dw(delt,slk[i]);slk[i]=INF;}
if
(delt==INF) return true;
for
(i=1;i< =T;i++)
if (v[i]) d[i]+=delt;
return
false;
}
void Zkw_Flow()
{
int i;ans=0;
for (i=1;i<=T;i++) d[i]=0,slk[i]=INF;
do{
do {memset(v,0,sizeof(v));}while (aug(S,INF));
}while (!modlabel());
printf("%.2lf\n",(1.0*ans)/(1.0*n));
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int
i,j,z;tot=0;
scanf("%d%d",&m,&n);
for
(i=1;i<=n;i++)
for (j=1;j<=m;j++)
scanf("%d",&tt[i][j]);
S=n*m+n+1;T=S+1;
for
(i=1;i<=n;i++) add(S,i,1,0);
for
(i=1;i<=n;i++)
for (j=1;j<=m;j++)
for (z=1;z<=n;z++)
add(i,get(j,z),1,z*tt[i][j]);
for
(i=1;i<=m;i++)
for (j=1;j<=n;j++)
add(get(i,j),T,1,0);
Zkw_Flow();
return
0;
}