zkw费用流 java_zkw费用流

因为某些原因被迫学习

我们回顾普通费用流的做法,通过每次spfa找到最小费用的流.

但是每次只松弛一条路径,效率低下.

于是就有了类似最大流多路增广的算法------zkw费用流

算法1

使用spfa把S到所有点的距离全部算出来

然后满足在最短路上的点一定满足\(dis[u]+length(u,v)=dis[v]\)

根据这个条件,可以写出一个较优秀的算法

bool spfa() {

memset(dis, 127, sizeof(dis));

memset(vis, 0, sizeof(vis));

dis[S] = 0; q.push(S);

while (!q.empty()) {

int u = q.front(); q.pop(); in[u] = 0;

for (int i = lst[u]; i; i = g[i].nxt)

if (g[i].w && dis[g[i].to] > dis[u] + g[i].v) {

dis[g[i].to] = dis[u] + g[i].v;

if (!in[g[i].to]) in[g[i].to] = 1, q.push(g[i].to);

}

}

return dis[T] != dis[0];

}

LL ans;

int dfs(int u, int flow) {

if (u == T) {

ans += dis[T] * flow;

return flow;

}

vis[u] = 1; int fl = 0;

for (int &i = cur[u]; i; i = g[i].nxt) {

int v = g[i].to; if (vis[v]) continue;

if (g[i].w && dis[v] == dis[u] + g[i].v) {

int d = dfs(v, min(flow, g[i].w));

g[i].w -= d; g[i ^ 1].w += d; fl += d;

flow -= d; if (!flow) break;

}

}

return fl;

}

void Mcmf(){

while (spfa()) {

for (int i = 1; i <= T; i++)

cur[i] = lst[i];

dfs(S, 2147483647);

}

}

算法2

上述代码虽然效率不错,但是一些毒瘤题或者骗分还是跑不过.

我们发现,算法的瓶颈在于spfa?

一次增广后,\(S->T\)的最短路会发生一些变化(一些边被流满惹)

这时通过再一次跑\(dfs\)可以把\(S\)能到的点找出来,然后被流满的边就是这个点集边缘的那些边.

我们就可以快速更新最短路了.

具体实现是维护\(T\)到其他点的最短路,然后松弛点集中的点.

bool spfa() {

queue que;

for (int i = 0; i <= N; i++) dis[i] = INF;

dis[T] = 0, inq[T] = 1, que.push(T);

while (!que.empty()) {

int x = que.front(); que.pop();

for (int i = fir[x]; ~i; i = e[i].next) {

int v = e[i].to, w = e[i ^ 1].cost;

if (e[i ^ 1].cap && dis[v] > dis[x] + w) {

dis[v] = dis[x] + w;

if (!inq[v]) que.push(v), inq[v] = 1;

}

}

inq[x] = 0;

}

return dis[S] != INF;

}

int tim, vis[MAX_N];

bool relabel() {

int res = INF;

for (int x = 0; x <= N; x++) {

if (vis[x] != tim) continue;

for (int i = fir[x]; ~i; i = e[i].next) {

int v = e[i].to;

if (e[i].cap && vis[v] != tim) res = min(res, dis[v] + e[i].cost - dis[x]);

}

}

if (res == INF) return 0;

for (int i = 0; i <= N; i++) if (vis[i] == tim) dis[i] += res;

return 1;

}

int dfs(int x, int f) {

if (x == T) return f;

vis[x] = tim;

int res = 0;

for (int &i = cur[x]; ~i; i = e[i].next) {

int v = e[i].to, w = e[i].cost;

if (e[i].cap && dis[x] == dis[v] + w && vis[v] != tim) {

int d = dfs(v, min(f, e[i].cap));

res += d, f -= d;

e[i].cap -= d, e[i ^ 1].cap += d;

if (!f) break;

}

}

return res;

}

void zkw() {

spfa();

int flow = 0, res = 0;

do {

int f = 0;

do {

for (int i = 0; i <= N; i++)

cur[i] = fir[i];

++tim;

f = dfs(S, INF);

flow += f, res += dis[S] * f;

} while (f);

} while (relabel());

printf("%d %d\n", flow, res);

}

代码是蒯的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值