洛谷P3381 【模板】最小费用最大流

P3381 【模板】最小费用最大流

最大流算法中的DFS搜索修改为SPFA算法,每次取增广路径时,都取成本最低,即最近路径。
可以得73分,三组数据超时,可能是跑进了死循环(其实并不是,SPFA没错),没发现错误,还是跟着答案学学吧。

#include<iostream>
#include<algorithm>
#include<queue>
#include<memory.h>
using namespace std;

const int MAXN = 5e3 + 10, MAX = 5e5 + 10;
const int INF = 0x3f3f3f3f;
int cnt = 0;
struct link {
	int v, w, c;
	int next;
}edge[MAX];
int head[MAXN];

int getk(int u, int v) {
	int k;
	for (k = head[u]; k; k = edge[k].next) {
		if (edge[k].v == v)return k;
	}
	return 0;
}


void addedge(int u, int v, int w, int c) {
	edge[++cnt].v = v; edge[cnt].w = w; edge[cnt].c = c;
	edge[cnt].next = head[u]; head[u] = cnt;
}
int n, m, s, t;

int pre[MAXN];
int dist[MAXN];
bool flag[MAXN];
int node[MAXN];

bool SPFA() {
	memset(dist, 0x3f, sizeof(dist));
	memset(flag, 0, sizeof(flag));
	memset(pre, 0, sizeof(pre));
	queue<int>q;
	q.push(s);
	node[s] = INF;
	dist[s] = 0;
	flag[s] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		flag[u] = 0;
		for (int i = head[u]; i; i = edge[i].next) {
			int v = edge[i].v, c = edge[i].c, w = edge[i].w;
			if (w&&dist[v] > dist[u] + c) {
				dist[v] = dist[u] + c;
				pre[v] = u;
				node[v] = min(node[u], w);
				if (!flag[v]) {
					flag[v] = 1;
					q.push(v);
				}
			}
		}
	}
	if (dist[t] >= INF)return 0;
	return 1;
}

void EK() {
	int ans = 0, all = 0;
	while (SPFA()) {
		int v = t, u;
		while (v != s) {
			u = pre[v];
			edge[getk(u, v)].w -= node[t];
			addedge(v, u, node[t], -edge[getk(u, v)].c);
			v = u;
		}
		ans += node[t];
		all += node[t] * dist[t];
	}
	cout << ans << ' ' << all << endl;
}


int main() {
	cin >> n >> m >> s >> t;

	int u, v, w, c;
	for (int i = 0; i < m; i++) {
		cin >> u >> v >> w >> c;
		addedge(u, v, w, c);
	}

	EK();

	system("pause");
	return 0;
}

为什么会超时呢?是静态链表更改时的问题。。。当时感觉很绕,但是没想到更好的解决办法。我们写的代码在更新边的剩余流量时,是通过记录两个端点,这就需要遍历一个链表去找到这个边的储存位置时间复杂度是 O ( n ) O(n) O(n)。洛谷中有三个数据会多次更改边的流量,这样就被卡了。

如何优化呢?在记录pre数组时,不在记录端点去寻找边的储存位置,而是直接储存下当前储存位置及数组下标。

另一个问题就是在减少一个边的流量时,需要增加这个边相反边的流量,即创造反向边,反向边如何定位呢?如果使用端点寻找时间复杂度是 O ( n ) O(n) O(n)

这个需要我们在储存时就开始处理,我们在输入一个边时就,一同将其反向边建立,我们将一个边储存在数组下标2的位置,将其反向边储存在下标3的位置那么我们对其中的一个边做 i 1 i^1 i1运算我们就得到了另一条。如此储存所有的边与反向边,这样我们就可以通过一个边位置求出其反向边的位置。

#include<iostream>
#include<algorithm>
#include<queue>
#include<memory>
using namespace std;

const int MAXN = 5e3 + 10, MAX = 5e5 + 10;
const int INF = 0x3f3f3f3f;
int cnt = 1;
struct link {
	int v, w, c;
	int next;
}edge[MAX];
int head[MAXN];

void addedge(int u, int v, int w, int c) {
	edge[++cnt].v = v; edge[cnt].w = w; edge[cnt].c = c;
	edge[cnt].next = head[u]; head[u] = cnt;
}
int n, m, s, t;

int pre[MAXN];
int dist[MAXN];
bool flag[MAXN];
int node[MAXN];

bool SPFA() {
	memset(dist, 0x3f, sizeof(dist));
	memset(flag, 0, sizeof(flag));
	memset(pre, 0, sizeof(pre));
	queue<int>q;
	q.push(s);
	node[s] = INF;
	dist[s] = 0;
	flag[s] = 1;
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		flag[u] = 0;
		for (int i = head[u]; i; i = edge[i].next) {
			int v = edge[i].v, c = edge[i].c, w = edge[i].w;
			if (w&&dist[v] > dist[u] + c) {
				dist[v] = dist[u] + c;
				pre[v] = i;
				node[v] = min(node[u], w);
				if (!flag[v]) {
					flag[v] = 1;
					q.push(v);
				}
			}
		}
	}
	if (dist[t] >= INF)return 0;
	return 1;
}

void EK() {
	int ans = 0, all = 0;
	while (SPFA()) {
		int v = t, i;
		while (v != s) {
			i = pre[v];
			edge[i].w -= node[t];
			edge[i ^ 1].w += node[t];
			v = edge[i ^ 1].v;
		}
		ans += node[t];
		all += node[t] * dist[t];
	}
	cout << ans << ' ' << all << endl;
}


int main() {
	cin >> n >> m >> s >> t;

	int u, v, w, c;
	for (int i = 0; i < m; i++) {
		cin >> u >> v >> w >> c;
		addedge(u, v, w, c);
		addedge(v, u, 0, -c);
	}

	EK();

	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值