1. 程式人生 > >POJ - 2135 - Farm Tour(最小費用最大流)

POJ - 2135 - Farm Tour(最小費用最大流)

POJ - 2135 - Farm Tour(最小費用最大流)

When FJ’s friends visit him on the farm, he likes to show them around. His farm comprises N (1 <= N <= 1000) fields numbered 1…N, the first of which contains his house and the Nth of which contains the big barn. A total M (1 <= M <= 10000) paths that connect the fields in various ways. Each path connects two different fields and has a nonzero length smaller than 35,000.

To show off his farm in the best way, he walks a tour that starts at his house, potentially travels through some fields, and ends at the barn. Later, he returns (potentially through some fields) back to his house again.

He wants his tour to be as short as possible, however he doesn’t want to walk on any given path more than once. Calculate the shortest tour possible. FJ is sure that some tour exists for any given farm.
Input

  • Line 1: Two space-separated integers: N and M.

  • Lines 2…M+1: Three space-separated integers that define a path: The starting field, the end field, and the path’s length.
    Output
    A single line containing the length of the shortest tour.
    Sample Input
    4 5
    1 2 1
    2 3 1
    3 4 1
    1 3 2
    2 4 2
    Sample Output
    6

  • 題目大意:
    一個農場主,要帶人逛他的農場,農場裡有一些穀倉,還有個大谷倉,他想先到大谷倉,再回到家,在這個過程中,把所有的點都走一遍。每一條路都有個距離,還要求距離最小而且相同的邊不能走第二次。
    抽象一下就是,有一個圖有n個結點,m條邊,要求從1號結點走到n號結點,在走回1號結點,經過所有的點,並要求路程最短而且相同的邊不能走第二次。
  • 解題思路
    我們把這個問題抽象成一個最小費用最大流問題。
    1.因為每條邊只能走一次,所以把每條邊的容量都設為1
    2.因為只需要過去,再回來,所以只要兩條路就夠了,我們只需找兩次增廣路就夠了
    3.因為要求最短的路,所以我們每次找增廣路的時候,我們用spfa找1-n的最短路。

因為這是我第一個最小費用最大流的問題,就在這裡簡單介紹一下我理解的最小費用最大流,菜雞一個,如有不對,歡迎指正。

  • 最小費用最大流
    定義:每個邊 G=(v,u)除了給定的容量C(u,v)之外,還給了一個單位流量的費用 W(u,j),所謂的最小費用最大流問題就是求一個最大流f,使得流得總運輸費最小。
    簡單來說就是,找費用最小的最大流。我的這篇部落格裡有對最大流的詳細解釋https://blog.csdn.net/weixin_43179892/article/details/84351135
    那麼我們具體怎麼解決呢?
    首先我們想到在解決最大流問題的時候,我們是不停的找從源點到匯點的增廣路,在這裡,我們還是找增廣路,但是要求費用最小,所以我們就每次找從源點到匯點的最短路,用SPFA找。不停的更新最大流,一直到更新不能更新為止,因為我們每次找的都是最短路,所以,每一條都是最小的花費,所以總的花費就是最小的了。
    具體求解步驟:(引自大佬部落格大佬部落格
    1)找到一條從源點到達匯點的“距離最短”的路徑,“距離”使用該路徑上的邊的單位費用之和來衡量。
    (2)然後找出這條路徑上的邊的容量的最小值f,則當前最大流max_flow擴充f,同時當前最小費用min_cost擴充 f*min_dist(s,t)。
    (3)將這條路徑上的每條正向邊的容量都減少f,每條反向邊的容量都增加f。
    (4)重複(1)–(3)直到無法找到從源點到達匯點的路徑。

當然具體的實現細節實在不好一一在這裡解釋,很繁瑣。個人認為可以把這個當成板子,然後理解好每一行程式碼的含義,會用就好。我這個是我看了別人的板子,自己敲得,加了些註釋,希望能讓你更好的理解一下。

  • AC程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn=1e5+10;
struct node{
	int to;
	int cap;///還能過多少流量
	int flow;//已經過了多少流量
	int cost;//花費
	int next;
}side[maxn];
int n,m;//點的個數 邊的個數
int head[maxn];
int pre[maxn];
int path[maxn];
int dis[maxn];
int vis[maxn];
int cnt=0;
void init()
{
	memset(head,-1,sizeof(head));
	memset(pre,-1,sizeof(pre));
	cnt=0;
}
void  add_side(int u,int v,int cap ,int cost)
{
	side[cnt].to=v;
	side[cnt].cap=cap;
	side[cnt].cost=cost;
	side[cnt].next=head[u];
	side[cnt].flow=0;
	head[u]=cnt++;
	///反向弧
	side[cnt].to=u;
	side[cnt].cap=0;
	side[cnt].cost=-cost;///注意這個地方是-cost
	side[cnt].next=head[v];
	side[cnt].flow=0;
	head[v]=cnt++;
}

bool spfa(int s,int t)//找最短路,就是找一條最小花費的增廣路
{
	memset(pre,-1,sizeof(pre));
	memset(dis,0x3f3f3f3f,sizeof(dis));
	memset(vis,0,sizeof(vis));
	vis[s]=1;
	dis[s]=0;
	queue<int> q;
	q.push(s);
	while(q.size())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=side[i].next)
		{
			int v=side[i].to;
			if(side[i].cap>side[i].flow&& dis[u]+side[i].cost<dis[v])///兩個條件 首先得有殘餘量,其次要能鬆弛
			{
				dis[v]=dis[u]+side[i].cost;
				pre[v]=i;//v指的邊的編號
				if(!vis[v])
				{
					vis[v]=1;
					q.push(v);
				}
				
			}
		}
	}
	if(pre[t]==-1)//說明沒有找到最後一個點 也就是沒有增廣路了
		return false ;
	return true;
}

int MincostMaxflow(int s,int t,int &cost)///返回最大流 並 把最小花費儲存在cost裡
{
	int flow=0;
	cost=0;
	while(spfa(s,t))
	{
		spfa(s,t);
		int f=0x3f3f3f3f;
		for(int u=pre[t];u!=-1;u=pre[side[u^1].to])
		{
			if(f>side[u].cap-side[u].flow)//一條增廣路上的最小的流就是增廣的值,cap-flow 增廣的值
				f=side[u].cap-side[u].flow;
		}
		flow+=f;///加到總的流量上

		for(int i=pre[t];i!=-1;i=pre[side[i^1].to])//更新邊 加 反向邊
		{
			side[i].flow+=f;
			side[i^1].flow-=f;
			cost+=side[i].cost*f;
		}
		if(flow==2)
			return flow;
	}
	return flow;
}
int main()
{
	cin>>n>>m;
	init();
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		add_side(a,b,1,c);//流量設為1
		add_side(b,a,1,c);
	}
	int cost=0;

	MincostMaxflow(1,n,cost);
	cout<<cost<<endl;
	return 0;
}