POJ-2135 Farm Tour(费用流)

本文介绍如何使用费用流算法解决农场中从房子到仓库再返回的最短路径问题,确保每条路仅走一次。通过构建特殊网络图,将问题转化为最大流最小费用问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

农场有N块田(1...N),1号是FJ住的房子,N号是大型仓库,一些田之间有双向路连接,且有一个路径长度,从房子出发到达大型仓库,然后再回到房子,要求对于每条路,只能走一次,求往返的最短距离。


思路:

经典费用流问题。我们可以通过设置一条边上的流量为1来表示每条边只走一次的限制,然后每个边的花费就是该路径长度。所以最关键的是如何表示我们要求两条最短路呢,我们去看每个点被选择了多少次,只有发现起点1和终点N各被选择了两次,其它点都只选择了一次,因此问题就变成了:最大流为2的时候最小费用为多少。所以我们建立一个超级源点连接1号点,容量为2,花费为0,即可实现选择1号点两次,同理建立一个超级汇点连接n号点,容量为2,花费为0,实现选择n号点两次。其它节点容量为1,花费为路径长度。


注意:

其实只建立一个超级源点或者只建立一个超级汇点也可行,其实就是一个点选择两次,另一个点选择无限制次,所以这样也可行。另外图是无向图,所以要在加边时,原图中的一条边要变成网络流图中的两条边(如果把反向负权费用边也算上就总共4条边)。


反思:

无向图对于一条无向边需要建立四条边:u->v边及其反向边和v->u及其反向边,无向图一条边的对应边存在是不会影响反向边的作用的,也不会造成其他影响。以前一直以为反向边和当前边在无向图中的相对边是一个概念,其实不是,而且这也打消了之前的一个疑惑:存在一个已经存在的边的对应边网络流会出错误。所以以后是有向图就建一条边及其反向边,是无向图就建两条相对的边及它们的反向边即可。


收获:

利用费用流解决问题,要抓住三点:一,费用是结果,所以题目本身需要时最值关系。二,最大流是条件,是关键也是难点,需要把题目中隐藏的最值条件挖掘出来。三,利用题目的限制条件来构造网络图。(参考)


代码:

#include <algorithm>
#include <iostream>
#include <string.h>
#include <cstdio>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1005;
const int maxm = 100020;
struct node
{
	int v, c, w, next;
} edge[maxm];
int no, head[maxn];
int n, m;
int S, T;
int vis[maxn], dis[maxn];
queue<int> q;
int pre[maxn], rec[maxn];
inline void init()
{
	no = 0;
	memset(head, -1, sizeof head);
}
inline void add(int u, int v, int w, int c)
{
	edge[no].v = v; edge[no].w = w;
	edge[no].c = c; edge[no].next = head[u];
	head[u] = no++;
	
	edge[no].v = u; edge[no].w = 0;
	edge[no].c = -c; edge[no].next = head[v];
	head[v] = no++;
}
bool SPFA()
{
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	while(!q.empty()) q.pop();
	pre[S] = S; dis[S] = 0;
	q.push(S); vis[S] = 1;
	while(!q.empty())
	{
		int top = q.front(); q.pop();
		vis[top] = 0;
		for(int k = head[top]; k != -1; k = edge[k].next)
		{
			if(edge[k].w && dis[edge[k].v] > dis[top]+edge[k].c)
			{
				dis[edge[k].v] = dis[top]+edge[k].c;
				pre[edge[k].v] = top; rec[edge[k].v] = k;
				if(!vis[edge[k].v]) 
				vis[edge[k].v] = 1, q.push(edge[k].v);
			}
		}
	}
	if(dis[T] == inf) return false;
	return true;
}
pair<int, int> mincost_maxflow()
{
	int mincost = 0, maxflow = 0;
	while(SPFA())
	{
		int flow = inf;
		for(int k = T; k != S; k = pre[k])
		flow = min(flow, edge[rec[k]].w);
		maxflow += flow;
		for(int k = T; k != S; k = pre[k])
		{
			mincost += flow*edge[rec[k]].c;
			edge[rec[k]].w -= flow;
			edge[rec[k]^1].w += flow;
		}
	}
	return make_pair(maxflow, mincost);
}
int main()
{
	int u, v, c;
	scanf("%d %d", &n, &m); init();
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d %d %d", &u, &v, &c);
		add(u, v, 1, c);
		add(v, u, 1, c);
	}
	S = 0, T = n+1;
	add(S, 1, 2, 0);
	add(n, T, 2, 0);
	pair<int, int> pr = mincost_maxflow();
	printf("%d\n", pr.second);
	return 0;
}

继续加油~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值