1. 程式人生 > >poj 3164 && tju 2248 最小樹形圖 朱劉演算法

poj 3164 && tju 2248 最小樹形圖 朱劉演算法

個人覺得這個部落格把這個演算法說的比較詳細了,直接搬過來吧,我再闡述一遍的話沒有人家說的好,還容易說錯。
========================== 分割線之下摘自Sasuke_SCUT的blog==================================================
最 小樹形圖,就是給有向帶權圖中指定一個特殊的點root,求一棵以root為根的有向生成樹T,並且T中所有邊的總權值最小。最小樹形圖的第一個演算法是 1965年朱永津和劉振巨集提出的複雜度為O(VE)的演算法。
判斷是否存在樹形圖的方法很簡單,只需要以v為根作一次圖的遍歷就可以了,所以下面的 演算法中不再考慮樹形圖不存在的情況。
在所有操作開始之前,我們需要把圖中所有的自環全都清除。很明顯,自環是不可能在任何一個樹形圖上的。只有進 行了這步操作,總演算法複雜度才真正能保證是O(VE)。
首先為除根之外的每個點選定一條入邊,這條入邊一定要是所有入邊中最小的。現在所有的最小 入邊都選擇出來了,如果這個入邊集不存在有向環的話,我們可以證明這個集合就是該圖的最小樹形圖。這個證明並不是很難。如果存在有向環的話,我們就要將這 個有向環所稱一個人工頂點,同時改變圖中邊的權。假設某點u在該環上,並設這個環中指向u的邊權是in[u],那麼對於每條從u出發的邊(u, i, w),在新圖中連線(new, i, w)的邊,其中new為新加的人工頂點; 對於每條進入u的邊(i, u, w),在新圖中建立邊(i, new, w-in[u])的邊。為什麼入邊的權要減去in[u],這個後面會解釋,在這裡先給出演算法的步驟。然後可以證明,新圖中最小樹形圖的權加上舊圖中被收縮 的那個環的權和,就是原圖中最小樹形圖的權。
上面結論也不做證明了。現在依據上面的結論,說明一下為什麼出邊的權不變,入邊的權要減去in [u]。對於新圖中的最小樹形圖T,設指向人工節點的邊為e。將人工節點展開以後,e指向了一個環。假設原先e是指向u的,這個時候我們將環上指向u的邊 in[u]刪除,這樣就得到了原圖中的一個樹形圖。我們會發現,如果新圖中e的權w'(e)是原圖中e的權w(e)減去in[u]權的話,那麼在我們刪除 掉in[u],並且將e恢復為原圖狀態的時候,這個樹形圖的權仍然是新圖樹形圖的權加環的權,而這個權值正是最小樹形圖的權值。所以在展開節點之後,我們 得到的仍然是最小樹形圖。逐步展開所有的人工節點,就會得到初始圖的最小樹形圖了。
如果實現得很聰明的話,可以達到找最小入邊O(E),找環 O(V),收縮O(E),其中在找環O(V)這裡需要一點技巧。這樣每次收縮的複雜度是O(E),然後最多會收縮幾次呢?由於我們一開始已經拿掉了所有的 自環,我門可以知道每個環至少包含2個點,收縮成1個點之後,總點數減少了至少1。當整個圖收縮到只有1個點的時候,最小樹形圖就不不用求了。所以我們最 多隻會進行V-1次的收縮,所以總得複雜度自然是O(VE)了。由此可見,如果一開始不除去自環的話,理論複雜度會和自環的數目有關。
======================== 分割線之上摘自

Sasuke_SCUT的blog=====================================================

下 面是朱劉演算法的構造圖


#include <cstdio>
#include <iostream>
#include<queue>
#include<set>
#include<ctime>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const double eps=1e-10;
#define M 109
#define type double 
const type inf=(1)<<30;
struct point 
{
	double x,y;
}p[M];
double dis(point a,point b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
struct Node{
	int u , v;
	type cost;
}E[M*M+5];
int pre[M],ID[M],vis[M];
type In[M];
int n,m; 
type Directed_MST(int root,int NV,int NE) {
	type ret = 0;
	while(true) {
		//1.找最小入邊
		for(int i=0;i<NV;i++) In[i] = inf;
		for(int i=0;i<NE;i++){
			int u = E[i].u;
			int v = E[i].v;
			if(E[i].cost < In[v] && u != v) {
				pre[v] = u;
				In[v] = E[i].cost;
			}
		}
		for(int i=0;i<NV;i++) {
			if(i == root) continue;
			if(In[i] == inf)	return -1;//除了跟以外有點沒有入邊,則根無法到達它
		}
		//2.找環
		int cntnode = 0;
	//	CC(ID,-1);
	//	CC(vis,-1);
	memset(ID,-1,sizeof(ID));
	memset(vis,-1,sizeof(vis));
		In[root] = 0;
		for(int i=0;i<NV;i++) {//標記每個環
			ret += In[i];
			int v = i;
			while(vis[v] != i && ID[v] == -1 && v != root) {
				vis[v] = i;
				v = pre[v];
			}
			if(v != root && ID[v] == -1) {
				for(int u = pre[v] ; u != v ; u = pre[u]) {
					ID[u] = cntnode;
				}
				ID[v] = cntnode ++;
			}
		}
		if(cntnode == 0)	break;//無環
		for(int i=0;i<NV;i++) if(ID[i] == -1) {
			ID[i] = cntnode ++;
		}
		//3.縮點,重新標記
		for(int i=0;i<NE;i++) {
			int v = E[i].v;
			E[i].u = ID[E[i].u];
			E[i].v = ID[E[i].v];
			if(E[i].u != E[i].v) {
				E[i].cost -= In[v];
			}
		}
		NV = cntnode;
		root = ID[root];
	}
	return ret;
}


int main()
{
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		// memset(pre,0,sizeof(pre));
		for(int i=0;i<n;i++)
		scanf("%lf%lf",&p[i].x,&p[i].y);
		for(int i=0;i<m;i++)
		{
		scanf("%d%d",&E[i].u,&E[i].v);
		E[i].u--;
		E[i].v--;
		if(E[i].u!=E[i].v)
		E[i].cost=dis(p[E[i].u],p[E[i].v]);
		else E[i].cost=1<<30;
		}
		type ans=Directed_MST(0,n,m);
		if(ans==-1)
		printf("poor snoopy\n");
		else 
		printf("%.2f\n",ans);
	}
	return 0;
}

#include <cstdio>
#include <iostream>
#include<queue>
#include<set>
#include<ctime>
#include<algorithm>
#include<cmath>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const double eps=1e-10;
#define M 109
#define type int
const type inf=(1)<<30;
struct point 
{
	double x,y;
}p[M];
double dis(point a,point b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
struct Node{
	int u , v;
	type cost;
}E[M*M+5];
int pre[M],ID[M],vis[M];
type In[M];
int n,m; 
type Directed_MST(int root,int NV,int NE) {
	type ret = 0;
	while(true) {
		//1.找最小入邊
		for(int i=0;i<NV;i++) In[i] = inf;
		for(int i=0;i<NE;i++){
			int u = E[i].u;
			int v = E[i].v;
			if(E[i].cost < In[v] && u != v) {
				pre[v] = u;
				In[v] = E[i].cost;
			}
		}
		for(int i=0;i<NV;i++) {
			if(i == root) continue;
			if(In[i] == inf)	return -1;//除了跟以外有點沒有入邊,則根無法到達它
		}
		//2.找環
		int cntnode = 0;
	memset(ID,-1,sizeof(ID));
	memset(vis,-1,sizeof(vis));
		In[root] = 0;
		for(int i=0;i<NV;i++) {//標記每個環
			ret += In[i];
			int v = i;
			while(vis[v] != i && ID[v] == -1 && v != root) {
				vis[v] = i;
				v = pre[v];
			}
			if(v != root && ID[v] == -1) {
				for(int u = pre[v] ; u != v ; u = pre[u]) {
					ID[u] = cntnode;
				}
				ID[v] = cntnode ++;
			}
		}
		if(cntnode == 0)	break;//無環
		for(int i=0;i<NV;i++) if(ID[i] == -1) {
			ID[i] = cntnode ++;
		}
		//3.縮點,重新標記
		for(int i=0;i<NE;i++) {
			int v = E[i].v;
			E[i].u = ID[E[i].u];
			E[i].v = ID[E[i].v];
			if(E[i].u != E[i].v) {
				E[i].cost -= In[v];
			}
		}
		NV = cntnode;
		root = ID[root];
	}
	return ret;
}


int main()
{
	while(scanf("%d%d",&n,&m),n+m)
	{
		for(int i=0;i<m;i++)
		{
		scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost);
		E[i].u--;
		E[i].v--;
		}
		type ans=Directed_MST(0,n,m);
		if(ans==-1)
		printf("impossible\n");
		else 
		printf("%d\n",ans);
	}
	return 0;
}