1. 程式人生 > >圖論:HDU-4009 Transfer water (最小樹形圖模板)

圖論:HDU-4009 Transfer water (最小樹形圖模板)

XiaoA lives in a village. Last year flood rained the village. So they decide to move the whole village to the mountain nearby this year. There is no spring in the mountain, so each household could only dig a well or build a water line from other household. If the household decide to dig a well, the money for the well is the height of their house multiplies X dollar per meter. If the household decide to build a water line from other household, and if the height of which supply water is not lower than the one which get water, the money of one water line is the Manhattan distance of the two households multiplies Y dollar per meter. Or if the height of which supply water is lower than the one which get water, a water pump is needed except the water line. Z dollar should be paid for one water pump. In addition,therelation of the households must be considered. Some households may do not allow some other households build a water line from there house. Now given the 3‐dimensional position (a, b, c) of every household the c of which means height, can you calculate the minimal money the whole village need so that every household has water, or tell the leader if it can’t be done.

Input

Multiple cases. 
First line of each case contains 4 integers n (1<=n<=1000), the number of the households, X (1<=X<=1000), Y (1<=Y<=1000), Z (1<=Z<=1000). 
Each of the next n lines contains 3 integers a, b, c means the position of the i‐th households, none of them will exceeded 1000. 
Then next n lines describe the relation between the households. The n+i+1‐th line describes the relation of the i‐th household. The line will begin with an integer k, and the next k integers are the household numbers that can build a water line from the i‐th household. 
If n=X=Y=Z=0, the input ends, and no output for that. 

Output

One integer in one line for each case, the minimal money the whole village need so that every household has water. If the plan does not exist, print “poor XiaoA” in one line. 

Sample Input

2 10 20 30
1 3 2
2 4 1
1 2
2 1 2
0 0 0 0

Sample Output

30


        
  

Hint

In  3‐dimensional  space  Manhattan  distance  of  point  A  (x1,  y1,  z1)  and  B(x2,  y2,  z2)  is |x2‐x1|+|y2‐y1|+|z2‐z1|. 

題意描述:在一個村莊有n戶人家(x,y,h),現在打算在村莊中挖井和建水渠使每戶人家都可以用上水

1、如果挖井費用為高度h*X;

2、如果a->b建水渠,if(a.h>=b.h)費用為兩點距離dis*Y,反之費用為dis*Y+Z

問,怎樣挖井和建水渠才能使費用最小?

分析:我們可以新建一個源點指向所有的使用者,權值為挖井的費用,然後根據是否能建水渠構建個使用者之間的關係,求圖的最小樹形圖即可

 #include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#define MAXN 1010
#define INF 0x7fffffff
using namespace std;
struct Point
{
	int x,y,z;
} p[MAXN];
struct Edge
{
	int u,v,cost;
	Edge() {}
	Edge(int u,int v,int cost):u(u),v(v),cost(cost) {}
} edge[MAXN*MAXN];
int n,m,X,Y,Z;
inline int get_cost(Point a,Point b)
{
	int dis=abs(a.x-b.x)+abs(a.y-b.y)+abs(a.z-b.z);
	if(a.z>=b.z) return dis*Y;
	return dis*Y+Z;

}
int pre[MAXN];///記錄當前頂點的前驅頂點
int id[MAXN];///經過縮點之後:原圖頂點號->新圖頂點號
int in[MAXN];///記錄頂點所有入邊的權值的最小值
int vis[MAXN];///是否訪問過當前頂點
inline int ZhuLiu(int root,int n,int m)
{
	int u,v;
	int ret=0;///最小樹形圖的權值
	while(true)
	{
		for(int i=0; i<=n; ++i) in[i]=INF;
		/*記錄頂點所有入邊的權值的最小值*/
		for(int i=0; i<m; ++i)
		{
			u=edge[i].u;
			v=edge[i].v;
			if(edge[i].cost<in[v]&&u!=v) ///去除v->v這種入邊
			{
				pre[v]=u;
				in[v]=edge[i].cost;
			}
		}
		/*除根節點外其他頂點每個點都必須有入邊,如果沒有則無法構成樹,返回INF表示不存在*/
		for(int i=0; i<=n; ++i)
		{
			if(i==root) continue;
			if(in[i]==INF) return INF;
		}
		in[root]=0;
		int cnt=0;///對每個頂點進行重新編號
		memset(id,-1,sizeof(id));
		memset(vis,-1,sizeof(vis));
		for(int i=0; i<=n; ++i)
		{
			ret+=in[i];
			v=i;
			/*構造環*/
			while(vis[v]!=i&&id[v]==-1&&v!=root)
			{
				vis[v]=i;
				v=pre[v];
			}
			/*判斷是否存在環,如果存在環則進行縮點,環中每一個頂點對應同一個頂點cnt*/
			if(v!=root&&id[v]==-1)
			{
				for(u=pre[v]; u!=v; u=pre[u]) id[u]=cnt;
				id[v]=cnt++;
			}
		}
		/*如果不存在環,則當前樹即為最小樹形圖*/
		if(cnt==0) break;
		/*對於其他不在環中的頂點進行編號*/
		for(int i=0; i<=n; ++i)
		{
			if(id[i]==-1) id[i]=cnt++;
		}
		/*對相應的邊進行修改*/
		for(int i=0; i<m; ++i)
		{
			v=edge[i].v;
			edge[i].u=id[edge[i].u];
			edge[i].v=id[edge[i].v];
			if(edge[i].u!=edge[i].v)
				edge[i].cost-=in[v];
		}
		n=cnt-1;
		root=id[root];
	}
	return ret;
}
int main()
{
	while(scanf("%d%d%d%d",&n,&X,&Y,&Z)!=EOF)
	{
		if(n==0&&X==0&&Y==0&&Z==0) break;
		for(int i=1; i<=n; ++i) scanf("%d%d%d",&p[i].x,&p[i].y,&p[i].z);
		int k,to;
		m=0;
		for(int i=1; i<=n; ++i)
		{
			scanf("%d",&k);
			while(k--)
			{
				scanf("%d",&to);
				edge[m++]=Edge(i,to,get_cost(p[i],p[to]));
			}
		}
		for(int i=1; i<=n; ++i)
			edge[m++]=Edge(0,i,p[i].z*X);
		printf("%d\n",ZhuLiu(0,n,m));
	}
	return 0;
}

一、相關定義

定義:設G = (V,E)是一個有向圖,它具有下述性質:

  1. G中不包含有向環; 
  2. 存在一個頂點vi,它不是任何弧的終點,而V中的其它頂點都恰好是唯一的一條弧的終點,則稱 G是以vi為根的樹形圖。

最小樹形圖就是有向圖G = (V, E)中以vi為根的樹形圖中權值和最小的那一個。

另一種說法:最小樹形圖,就是給有向帶權圖一個特殊的點root,求一棵以root為根節點的樹使得該樹的的總權值最小。

性質:最小樹形圖基於貪心和縮點的思想。

縮點:將幾個點看成一個點,所有連到這幾個點的邊都視為連到收縮點,所有從這幾個點連出的邊都視為從收縮點連出

二、演算法描述

【概述】

為了求一個圖的最小樹形圖,①先求出最短弧集合E0;②如果E0不存在,則圖的最小樹形圖也不存在;③如果E0存在且不具有環,則E0就是最小樹形圖;④如果E0存在但是存在有向環,則把這個環收縮成一個點u,形成新的圖G1,然後對G1繼續求其的最小樹形圖,直到求到圖Gi,如果Gi不具有最小樹形圖,那麼此圖不存在最小樹形圖,如果Gi存在最小樹形圖,那麼逐層展開,就得到了原圖的最小樹形圖。

【實現細節】

設根結點為v0,

  • (1)求最短弧集合E0

  從所有以vi(i ≠ 0)為終點的弧中取一條最短的,若對於點i,沒有入邊,則不存在最小樹形圖,演算法結束;如果能取,則得到由n個點和n-1條邊組成的圖G的一個子圖G',這個子圖的權值一定是最小的,但是不一定是一棵樹。

  • (2)檢查E0

  若E0沒有有向環且不包含收縮點,則計算結束,E0就是圖G以v0為根的最小樹形圖;若E0含有有向環,則轉入步驟(3);若E0沒有有向環,但是存在收縮點,轉到步驟(4)。

  • (3)收縮G中的有向環

  把G中的環C收縮成點u,對於圖G中兩端都屬於C的邊就會被收縮掉,其他弧仍然保留,得到一個新的圖G1,G1中以收縮點為終點的弧的長度要變化。變化的規則是:設點v在環C中,且環中指向v的邊的權值為w,點v'不在環C中,則對於G中的每一條邊<v', v>,在G1中有邊<v', u>和其對應,且權值WG1(<v', u>) = WG(<v', v>) - w;對於圖G中以環C中的點為起點的邊<v', v>,在圖G1中有邊<u, v'>,則WG1(<u, v'>) = WG(<v', v>)。有一點需要注意,在這裡生成的圖G1可能存在重邊。

  對於圖G和G1:

  ①如果圖G1中沒有以v0為根的最小樹形圖,則圖G也沒有;

  ②如果G1中有一v0為根的最小樹形圖,則可按照步驟(4)的展開方法得到圖G的最小樹形圖。

所以,應該對於圖G1代到(1)中反覆求其最小樹形圖,直到G1的最小樹形圖u求出。

  • (4)展開收縮點

  假設圖G1的最小樹形圖為T1,那麼T1中所有的弧都屬於圖G的最小樹形圖T。將G1的一個收縮點u展開成環C,從C中去掉與T1具有相同終點的弧,其他弧都屬於T。

【小結】

對最小樹形圖做個小小的總結:

1:清除自環,自環是不可能存在於任何最小樹形圖中的;

2:求出每個頂點的的最小入邊;

3:判斷該圖是否存在最小樹形圖,由 1 可以判定,或者以圖中頂點v作為根節點遍歷該圖就能判斷是否存在最小樹形圖;

4:找環,之後建立新圖,縮點後重新標記。

【圖示——最小樹形圖構造流程】

解讀:第一幅圖為原始圖G,首先對於圖G求其最短弧集合E0,即第二幅圖G1;然後檢查E0是滿足條件,在這裡,可以看到G1具有兩個環,那麼把這兩個環收縮,如第三幅圖所示,U1、U2分別為收縮後的點,然後將對應的權值進行更新,起點是環中的點,終點是環外的點,則權值不變。反之,起點是環外的點,終點是環內的點,則權值應該減去E0中指向環內點的權值,形成新的圖,如第三幅圖,對於其反覆求最小樹形圖,直到不存在最小樹形圖,或者求得縮點後的圖的最小樹形圖,然後展開就好了,如第六幅圖。

如果只要求計算權值的話,則不需要展開,所有環中權值的和加上其他各個點與點之間,或者收縮點和點之間的權值就是總的權值。