3068 "Shortest" pair of paths:最低花费的独立路径--最小费用流本流

题目翻译

题目链接
有N个可以储存化学药品的仓库(顶点)。 有M种单独的运输方法(边)连接多对仓库。 每种单独的运输方式都有成本。 在通常的问题中,公司将需要找到一种方法将单批货物从第一个仓库(0)运送到最后一个仓库(N-1)。 这很容易。 他们遇到的问题似乎更难了。 他们必须从第一个仓库(0)到最后一个仓库(N-1)运送两种化学品。 这些化学物质很危险,不能安全地放置在一起。 法规说该公司不能对两种化学品使用相同的运输方式。 此外,如果没有特殊的存储操作,该公司就无法将这两种化学药品放置在同一仓库中(任何时间长度)-仅在第一个和最后一个仓库中可用。 首先,他们需要知道是否有可能在这些限制下运送两种化学品。 接下来,他们需要找到将两种化学品从第一个仓库运输到最后一个仓库的最低成本。 简而言之,他们需要两条完全独立的路径(从第一个仓库到最后一个仓库),这两个路径的总成本最低。

输入

输入将包含多种情况。 每个输入的第一行将包含N和M,其中N是仓库的数量,M是单独运输方式的数量。 您可以假设N小于64,M小于10000。接下来的M行将包含三个值,i,j和v。每行对应一种唯一的装运方法。 值i和j是两个仓库的索引,而v是从i到j的成本。 请注意,这些运输方法是直接的。 如果可以以10美元的价格将某物从i运送到j,那么从j运送到i并没有任何意义。 同样,在任何一对仓库之间可能有不止一种运输方式,这在这里很重要。

包含两个零的行表示数据结束,因此不应进行处理。

思路分析

就冲这 完全独立的路径,我们的建图就变得无比简单,点和点之间容量为1即可,保证任何一条边不会使用两次以上。而不止一种运输方式其实影响不大,无需在意

#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;

#define MAX 70
#define inf 2222222222
#define ll long long

struct edge {
	ll to, cap, rev, cost;
	edge(ll a = 0, ll b = 0, ll c = 0, ll d = 0) { to = a, cap = b, rev = c, cost = d; }
};

vector<edge> G[MAX];
ll dist[MAX], vis[MAX];//dis:spfa中的最短距离 vis:spfa中细讲 
ll pre1[MAX], pre2[MAX], minf[MAX];//spfa中细讲
ll N, M, S, T, maxf = 0, minCost = 0;//最后两个:最大流与最小花费

void addEdge(ll from, ll to, ll cap, ll cost) {
	G[from].push_back(edge(to, cap, G[to].size(), cost));
	G[to].push_back(edge(from, 0, G[from].size() - 1, -cost));
}


bool spfa(ll s, ll t) {
	fill(dist + 1, dist + N + 1, inf);//初始化所有距离为无穷
	queue<ll>q;
	memset(vis, 0, sizeof(vis));//vis[i]=1:i已经加入了q中,不需要再次加入
	dist[s] = 0, q.push(s), vis[s] = true;
	minf[s] = inf;//记录的是一条增广路的最小流量。minf[i]代表当前增广路到i为止的最小流量,minf[t]为整条路最小流量。 
	while (!q.empty()) {
		ll id = q.front(); q.pop(); vis[id] = 0;
		for (unsigned i = 0; i < G[id].size(); i++) {
			edge & e = G[id][i];
			if (e.cap > 0 && dist[e.to] > dist[id] + e.cost) {//有流量才能松弛~ 
				dist[e.to] = dist[id] + e.cost;
				minf[e.to] = min(minf[id], e.cap);
				pre1[e.to] = id;//pre1[i]是这次增广路的i点是由哪个点流过来的。
				pre2[e.to] = i;//pre2[i]是这次增广路的i点是由pre1[i]的哪条边流过来的
				if (!vis[e.to]) {
					vis[e.to] = true; q.push(e.to);
				}
			}
		}
	}
	return dist[t] != inf;//不等于inf说明还能有流
}

void update(ll s, ll t) {
	ll x = t;//首先把当前点置为终点,我们沿着终点由增广路反向走到起点~ 
	while (x != s) {
		int Vid = pre1[x], Eid = pre2[x];//取出流向x点的Vid点与Eid边
		G[Vid][Eid].cap -= minf[t];所有增广路上的边一律减去可扩最大容量! 
		G[x][G[Vid][Eid].rev].cap += minf[t];//反向边,一律增加这个容量。
		x = Vid;
	}
	maxf += minf[t];
	minCost += minf[t] * dist[t];//流量*距离,每个边流量都一样
}

void minCostFlow(ll s, ll t) {
	while (spfa(s, t)) {
		update(s, t);
		if (maxf >= 2) {
			cout << minCost << endl;
			break;
		}
	}
	if (maxf < 2)cout << "Not possible" << endl;
}

int main() {
	int i = 1;
	while (cin >> N >> M && N + M > 0) {
		maxf = 0, minCost = 0;
		for (ll i = 0; i < N; i++) G[i].clear();//点序是从0开始的
		for (int j = 0; j < M; j++) {
			ll a, b, c; cin >> a >> b >> c;
			addEdge(a, b, 1, c);
		}
		printf("Instance #%d: ", i++);
		minCostFlow(0, N - 1);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值