dijkstra求费用流

一般求解费用流运用EK,然后可以单路或多路增广。

注意这里的多路增广要求一个点在一次dfs中不能被经过两次。因为可能出现零环或负环的情况。

而单路增广虽然效率低,但好写许多且在某些已经限制了总流量且单次流量都为1时有它的好处。

但在有些稠密图中,即使多路增广实际效果也不好,这时候可以采取dijkstra代替spfa。

这个跟johnson算法很像很像,也是维护一个势函数。

但与johnson算法不同的是,我们并不需要从原点向每个点连一条为0的边。

我们则可以暂且把势函数先全部赋值为0。

这是因为,在费用流的图中有一点很重要,就是一条负边你不可能一开始就走,必须是走过它正边以后把它抵消回去,换句话说,在费用流的图中一开始是绝对不会出现负环的(即使你一开始连的是负边,你依然不会走出负环)。所以这种情况下我们不需要像johnson一样求一遍spfa。

现在需要的是维护这个势函数,可以发现只需要把势函数加上这一遍dij求出来的 d i s dis dis值即可。

注意一点的是,求dijkstra的时候要注意一个小优化。

Code

多路增广

#pragma GCC optimize(3,"Ofast","inline")
#include <bits/stdc++.h>

#define I register int
#define F(i, a, b) for (int i = a; i <= b; i++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)

const int N = 400 + 10;
const int inf = 1e9;

using namespace std;

int n, m, x, y, c, w, tot, S,T, Ans, Sum,  dis[N], h[N], cur[N]; bool vis[N];
struct edge { int to, flow, cost, rev; };
vector <edge> G[N];
struct node {
	int x, dis;
	bool operator < (const node &a) const { return a.dis < dis; } //注意这里不能用friend去定义的,自定义的a就是要排的数,然后与当前这个位置dis进行比较
};

void ins(I x, I y, I w, I l) {
	G[x].push_back({y, w, l, G[y].size()});
	G[y].push_back({x, 0, - l, G[x].size() - 1});
} //当边数很多时用vector会显现优势,在O2下优势明显!!!
priority_queue <node> Q;
bool dijkstra() {
	mem(dis, 127), dis[S] = 0, Q.push({S, 0});
	while (Q.size()) {
		I k = Q.top().x, v = Q.top().dis; Q.pop();
		if (v > dis[k]) continue; //注意这个trick,这是当边数很多时,dis[k]有可能被更新很多次,但显然最小的那个才有用,所以我们只更新一次。这样可以保证复杂度是O(n^2+mlogn)的
		F(i, 0, G[k].size() - 1) {
			edge e = G[k][i];
			if (e.flow && dis[e.to] > dis[k] + e.cost + h[k] - h[e.to]) {
				dis[e.to] = dis[k] + e.cost + h[k] - h[e.to];
				Q.push({e.to, dis[e.to]});
			}
		}
	}
	return dis[T] < inf;
}

int DFS(I k, I flow) {
	if (k == T) return flow;
	I have = 0;
	vis[k] = 1;
	for (I &i = cur[k]; i < G[k].size(); i ++) { //当前弧优化
		edge &e = G[k][i];
		if (!vis[e.to] && e.flow && dis[e.to] == dis[k] + e.cost + h[k] - h[e.to]) {
			I now = DFS(e.to, min(flow - have, e.flow));
			e.flow -= now, G[e.to][e.rev].flow += now;
			have += now;
			if (flow == have) break;
		}
	}
	vis[k] = 0; //这里是多路增广的一个trick,但要注意当边数和点数不在一个量级时,这个要省略,也就是说我们尝试单路增广
	return have;
}

int main() {
	scanf("%d%d", &n, &m), tot = 1, S = 1, T = n;
    F(i, 1, m) {
        scanf("%d%d%d%d", &x, &y, &c, &w);
        ins(x, y, c, w);
    }

    Ans = 0;
	while (dijkstra()) {
		mem(cur, 0);
		int Flow = DFS(S, inf);
		F(i, S, T) h[i] += dis[i];
		Sum += Flow, Ans += h[T] * Flow; // h[S]一定为0,所以可以省略。
	}

    printf("%d %d\n", Sum, Ans);
}

单路增广

#include <bits/stdc++.h>

#define F(i, a, b) for (int i = a; i <= b; i++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define mem(a, b) memset(a, b, sizeof a)

const int N = 400 + 10;
const int M = 3e5 + 10;
const int inf = 2e9;

using namespace std;

int n, m, x, y, c, w, tot, S,T, Ans, Sum;
bool vis[N], bz[N], h[N];
int tov[M], nex[M], len[M], cost[M], pre[N], las[N], dis[N];
struct node {
	int x, dis;
	bool operator < (const node &a) const { return a.dis < dis; }
};

void ins(int x, int y, int c, int w) {
    tov[++tot] = y, nex[tot] = las[x], las[x] = tot, len[tot] = c, cost[tot] = w;
    tov[++tot] = x, nex[tot] = las[y], las[y] = tot, len[tot] = 0, cost[tot] = -w;
}

bool dijkstra() {
	F(i, 1, n) {
		h[i] = min(h[i] + dis[i], inf);
		dis[i] = i == S ? 0 : inf;
	}
	priority_queue <node> Q; Q.push({S, 0}); mem(vis, 0);
	while (Q.size()) {
		int k = Q.top().x, v = Q.top().dis; Q.pop();
		if (v > dis[k]) continue;
		for (int x = las[k]; x ; x = nex[x])
			if (len[x] && dis[tov[x]] > dis[k] + cost[x] + h[k] - h[tov[x]]) {
				dis[tov[x]] = dis[k] + cost[x] + h[k] - h[tov[x]];
				pre[tov[x]] = x;
				Q.push({tov[x], dis[tov[x]]});
			}
	}
	return dis[T] < inf;
}

int main() {
    scanf("%d%d", &n, &m), tot = 1, S = 1, T = n;
    F(i, 1, m) {
        scanf("%d%d%d%d", &x, &y, &c, &w);
        ins(x, y, c, w);
    }

    while (dijkstra()) {
       	Ans += dis[T] + h[T] - h[S];
       	int Flow = 2147483647;
       	for (x = T; x ^ S; x = tov[pre[x] ^ 1])
       		Flow = min(Flow, len[pre[x]]);
       	Sum += Flow;
       	for (x = T; x ^ S; x = tov[pre[x] ^ 1])
			len[pre[x]] -= Flow, len[pre[x] ^ 1] += Flow;
	}

    printf("%d %d\n", Sum, Ans);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值