zkw费用流 java_【网络流】【ZKW费用流 模板】+【例题 BZOJ1070 修车】

自己再稍总结一下

先定义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;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值