费用流-Dijkstra模板

文章凭空消失了几遍我已经不想打大标题了。再次声明不是我懒。(滑稽
其实这就是在最大流的情况之下(可能有很多个最大流),我们在每条边上都加一条边权,使得总的边权和最小。

其实dijkstra部分可以用spfa完成(很多题都跑的快些,只是怕被卡 )。我们发现这种算法跑不了负权图,那我们怎么办呢?就是将负权转化为正的。我们定义h数组,叫做势。

我们使 e ′ = e + h [ u ] − h [ v ] e'=e+h[u]-h[v] e=e+h[u]h[v]。那我们的目标就是 e + h [ u ] − h [ v ] > = 0 e+h[u]-h[v]>=0 e+h[u]h[v]>=0
移项得: e + h [ u ] > = h [ v ] e+h[u]>=h[v] e+h[u]>=h[v]。有木有感觉有点熟悉?没有我们换一个形式: e + d i s [ u ] > = d i s [ v ] e+dis[u]>=dis[v] e+dis[u]>=dis[v]
猜到了吧,我们可以将每次更新的dis作为h。

不过这还没完,我们其实无法确定u和v之间究竟有没有dis。如果没有呢?可以发现,这条边的反向边一定在上次就遍历过了。我们可以得出一个式子: − e + h [ v ] = = h [ u ] -e+h[v]==h[u] e+h[v]==h[u],即 h [ v ] = = h [ u ] + e h[v]==h[u]+e h[v]==h[u]+e

为什么这里取等呢?很明显,要想在这一次复活,上一次必须是最短路上的点啊。

最后再讲一下为什么这样不会影响最短路。其实手动模拟一下可以发现h是可以抵消的。

上马!

#pragma GCC optimize(2)
#include<cstdio>
#include<cctype>
#include<queue>
#include<cstring>
#define Pair pair <int, int>
using namespace std;

const int N = 5002, M = 900002, lim = 0x3f3f3f3f;
int f = lim, a[N][N], prev[N], pree[N], dis[N], s, t, n, m, cnt, val[M], flow[M], to[M], head[N], nxt[M], h[N];

inline int read() {
    int x = 0, f = 1;
    char s = getchar();
    while(! isdigit(s)) {
        if(s == '-')
            f = -1;
        s = getchar();
    }
    while(isdigit(s)) {
        x = (x << 1) + (x << 3) + (s ^ 48);
        s = getchar();
    }
    return x * f;
}

inline void print(int x) {
    if(x < 0) {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}

inline int Min(const int x, const int y) {
	if(x < y)
		return x;
	return y;
}

inline void addEdge(const int Val, const int w, const int v, const int u) {
	to[++ cnt] = v;
	nxt[cnt] = head[u]; 
	flow[cnt] = w;
	val[cnt] = Val;
	head[u] = cnt;
	to[++ cnt] = u;
	nxt[cnt] = head[v];
	flow[cnt] = 0;
	val[cnt] = -Val;
	head[v] = cnt;
}

Pair EK() {
	int res = 0, maxflow, ans = 0;
	while(f) {
		priority_queue <Pair, vector <Pair>, greater <Pair> > q;
		for(int i = 1; i <= n; ++ i)
			dis[i] = -1;
		dis[s] = 0;
		q.push(Pair(0, s));
		while(! q.empty()) {
			Pair now = q.top();
			q.pop();
			int u = now.second;
			if(dis[u] < now.first)
				continue;
			for(int i = head[u]; ~i; i = nxt[i]) {
				int v = to[i];
				if(flow[i] > 0 && (dis[v] < 0 || dis[v] > dis[u] + val[i] + h[u] - h[v])) {
					dis[v] = dis[u] + val[i] + h[u] - h[v];
					prev[v] = u;
					pree[v] = i;
					q.push(Pair(dis[v], v));
				}
			}
		}
		if(dis[t] < 0)
			break;
		maxflow = f;
		for(int i = 1; i <= n; ++ i)
			h[i] += (dis[i] == -1 ? 0 : dis[i]);
		for(int i = t; i != s; i = prev[i])
			maxflow = Min(maxflow, flow[pree[i]]);
		res += maxflow * h[t];
		ans += maxflow;
		f -= maxflow;
		for(int i = t; i != s; i = prev[i]) {
			flow[pree[i]] -= maxflow;
			flow[pree[i] ^ 1] += maxflow;
		}
	}
	return Pair(res, ans);
}

int main() {
	n = read();
	m = read();
	s = read();
	t = read();
	cnt = -1;
	memset(head, -1, sizeof head);
	for(int i = 1; i <= m; ++ i)
		addEdge(read(), read(), read(), read());
	Pair ret = EK();
	print(ret.second);
	putchar(' ');
	print(ret.first);
	putchar('\n');
	return 0;
}

如有错误,请在评论区指出,谢谢!(我根本没学懂

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值