1. 程式人生 > >【UVa】11354 Bond 最小生成樹,動態LCA,倍增思想

【UVa】11354 Bond 最小生成樹,動態LCA,倍增思想

Bond

Once again, James Bond is on his way tosaving the world. Bond's latest mission requires him to travel between severalpairs of cities in a certain country.

The country has N cities(numbered by 1, 2, . . .,N),connected byM bidirectionalroads. Bond is going to steal a vehicle, and drive along the roads from citys

to cityt. The country'spolice will be patrolling the roads, looking for Bond, however, not all roadsget the same degree of attention from the police.

More formally, for each road MI6has estimated its dangerousness, the higher it is, the more likely Bond isgoing to be caught while driving on this road. Dangerousness of a path froms

tot is defined asthe maximum dangerousness of any road on this path.

Now, it's your job to help Bond succeedin saving the world by finding the least dangerous paths for his mission.

Input

There will be at most 5 cases in theinput file.

The first line of each case contains twointegers N, M (2 ≤ N

≤ 50000,1≤ M ≤ 100000)– number of cities and roads. The nextM lines describe the roads. Thei-thof these lines contains three integers:xi,yi, di (1 ≤xi,yiN, 0 ≤di ≤ 109)- the numbers of the cities connected by theith road and itsdangerousness.

Description of the roads is followed bya line containing an integerQ (1 ≤Q ≤ 50000),followed by Q lines, thei-thof which contains two integerssi andti (1 ≤ si,ti  ≤N, si !=ti).

Consecutive input sets are separated bya blank line.

Output

For each case, output Q lines, thei-thof which contains the minimum dangerousness of a path between citiessi andti. Consecutiveoutput blocks are separated by a blank line.

The input filewill be such that there will always be at least one valid path.

Sample Input

Output for Sample Input

4 5

1 2 10

1 3 20

1 4 100

2 4 30

3 4 10

2

1 4

4 1

2 1

1 2 100

1

1 2

20

20

100

ProblemSetter: Ivan Krasilnikov 

題型:最小生成樹,動態LCA,倍增思想

題目大意:

有n座城市通過m條雙向道路相連,每條道路都有一個危險係數。你的任務是回答若干個詢問,每個詢問包含一個起點s和一個終點t,要求找到一條從s到t的路,使得途經所有邊的最大危險係數最小。

輸入最多包含5組資料。(2 <= n <= 50000 , 1 <= m <= 100000 )。詢問有Q個(1 <= Q <= 50000 )。起點編號1~n。

題目分析:

本題要求的是瓶頸路,而且需要快速作答,首先求所有點的瓶頸路肯定是不行的,陣列存不下。

那麼我們可以改變策略,考慮先預處理,將資訊組織成易於查詢的結構。

曾經學過倍增演算法(其實只是自己YY然後實現過,在ACDream的某道題上用過),但是畫風太差,現在又重新學了一個。

首先我們先求最小生成樹,考慮到是稀疏圖我們用Kruskal演算法求解,然後通過一次DFS將無根樹轉化成有根樹,同時求出每個結點u的深度deep[u](規定根結點深度為0,根結點的子節點深度為1,依此類推),父節點fa[u],以及它與父節點之間的路徑的權值cost[u]。

接下來預處理出anc和maxcost陣列,其中anc[i][j]表示結點i的第 2^j 級祖先編號(anc[i][0]就是父親fa[i],anc[i][j] = -1 表示該祖先不存在),maxcost[i][j]表示結點i與它的 2^j 級祖先之間路徑上的最大權值。預處理程式碼如下:

void preProcess () {//預處理出anc和maxcost陣列
		REPF ( i , 1 , n ) {
			anc[i][0] = fa[i] ;// i^0 級祖先就是父親
			maxcost[i][0] = cost[i] ;//i與fa[i]之間的最大權值就是cost[i]
			LOGF ( j , 1 , n )
				anc[i][j] = -1 ;
		}
		LOGF ( j , 1 , n )
			REPF ( i , 1 , n )
				if ( ~anc[i][j - 1] ) {
					int a = anc[i][j - 1] ;
					anc[i][j] = anc[a][j - 1] ;
					maxcost[i][j] = max ( maxcost[i][j - 1] , maxcost[a][j - 1] ) ;
					//選擇i~anc[i][j - 1]中的最大權值和anc[i][j - 1]~anc[anc[i][j - 1][j - 1]中的最大權值
					//也就是i ~ (i^(j-1)) 和 (i^(j-1)) ~ i^j 中選取最大權值(子段的最大權值已經求出)
				}
	}//preProcess

有了這寫預處理我們就可以編寫線上查詢處理過程了。假定查詢的兩個節點為p和q,並且p比q深(不是就交換),則可以先把p提升到和q同樣深的位置(假設樹是倒著長的),提升過程中先求出deep[q] + 1 ~ deep[p]之間的最大權值。然後利用二進位制展開的方法不斷將p和q同時往上“提”(先保證二者深度始終相等),同時更新最大邊權。程式碼如下:
int query ( int p , int q ) {//查詢兩點間的最小瓶頸路
		int tmp , log = 0 , ans = -OO ;
		if ( deep[p] < deep[q] )//令p的深度大於等於q,不滿足就交換
			swap ( p , q ) ;
		LOGF ( i , 1 , deep[p] + 1 )//得到p的最大log段( 滿足 ( 1 << log ) <= deep[p] , 1 << ( log + 1 ) > deep[p] )
			++ log ;
		REPV ( i , log , 0 )//將p的深度降低到與q相同,同時求出p到q深度之間的最大權值
			if ( deep[p] - ( 1 << i ) >= deep[q] ) {//第2^i級祖先的深度大於等於q
				ans = max ( ans , maxcost[p][i] ) ;
				p = anc[p][i] ;//跳到2^i級祖先的位置
			}
		if ( p == q )//q是p的祖先,則之前的處理直接讓p下降到q的位置,p、q之間的最大權值已經求出
			return ans ;//LCA返回p( p 等於 q )
		REPV ( i , log , 0 )//比較的前提是p、q深度相同
			if ( ~anc[p][i] && anc[p][i] != anc[q][i] ) {
				//p和q深度相同,判斷一個即可
				//同時祖先不能是同一個,保證所比較的都是唯一路徑上的邊,否則會跳出最近公共祖先,得到錯誤結果
				ans = max ( ans , maxcost[p][i] ) ;
				ans = max ( ans , maxcost[q][i] ) ;
				p = anc[p][i] ;//跳
				q = anc[q][i] ;//跳
			}
		ans = max ( ans , cost[p] ) ;
		ans = max ( ans , cost[q] ) ;
		return ans ;//LCA返回fa[p]( 它也等於fa[q] )
	}//query

上述程式碼同樣可以求出p和q的最近公共祖先。這樣就在預處理O(nlogn),查詢O(logn)的時間內解決了問題。

寫了一天,不過有收穫,感覺不錯,還需鞏固。

程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
#define REPV( i , a , b ) for ( int i = a ; i >= b ; -- i )
#define LOGF( j , a , b ) for ( int j = a ; ( 1 << j ) < b ; ++ j )
#define EDGE( i , x ) for ( int i = adj[x] ; ~i ; i = edge[i].n )
#define REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define clear( a , x ) memset ( a , x , sizeof a )

const int LOGN = 20 ;
const int MAXN = 50005 ;
const int MAXE = 100005 ;
const int OO = 0x3f3f3f3f ;
struct Line {
	int x , y , val ;
	void input () {
		scanf ( "%d%d%d" , &x , &y , &val ) ;
	}
	bool operator < ( const Line &a ) const {
		return val < a.val ;
	}
} ;

struct Edge {
	int v , w , n ;
	Edge ( int V = 0 , int W = 0 , int N  = 0 ) :
		v(V) , w(W) , n(N) {}
} ;

struct findAndUnion {
	int p[MAXN] ;
	int rank[MAXN] ;
	
	void init () {
		REP ( i , MAXN )
			p[i] = i , rank[i] = 1 ;
	}
	
	int find ( int x ) {//非遞迴查詢+路徑壓縮
		int tmp , o = x , ans ;
		while ( p[o] != o )
			o = p[o] ;
		ans = o ;
		o = x ;
		while ( p[o] != o ) {
			tmp = p[o] ;
			p[o] = ans ;
			o = tmp ;
		}
		return ans ;
	}//find
	
	int Union ( int x , int y ) {//合併(按秩合併)
		int f1 = find ( x ) ;
		int f2 = find ( y ) ;
		if ( f1 != f2 ) {
			if ( rank[f1] <= rank[f2] ) {//秩小的合併到秩大的點上
				p[f1] = f2 ;
				if ( rank[f1] == rank[f2] )
					++ rank[f2] ;
			}
			else
				p[f2] = f1 ;
			return 1 ;
		}
		return 0 ;
	}//union
} ;

struct MST {
	//並查集
	findAndUnion F ;
	int p[MAXN] ;//並查集父節點
	
	Line line[MAXE] ;//讀入邊集
	
	Edge edge[MAXE] ;//最小生成樹邊集
	int adj[MAXN] , cntE ;//表頭及指標
	
	//倍增處理及查詢(可用於LCA)
	int maxcost[MAXN][LOGN] ;//maxcost[i][j],最小瓶頸路
	int anc[MAXN][LOGN] ;//anc[i][j]表示結點i的第2^j級祖先,2^0就是父親
	int cost[MAXN] ;//cost[i]表示i與父親fa[i]之間的邊權
	int fa[MAXN] ;//父節點
	int deep[MAXN] ;//結點深度
	
	int n , m ;//結點數,邊數
	
	void init () {
		F.init () ;
		cntE = 0 ;
		clear ( adj , -1 ) ;
		clear ( deep , 0 ) ;
	}//init
	
	void addedge ( int u , int v , int w ) {
		edge[cntE] = Edge ( v , w , adj[u] ) ;
		adj[u] = cntE ++ ;
		edge[cntE] = Edge ( u , w , adj[v] ) ;
		adj[v] = cntE ++ ;
	}//addedge
	
	int kruskal () {//求最小生成樹
		sort ( line , line + m ) ;
		int cnt = 0 , ans = 0 ;
		REP ( i , m ) {
			int tmp = F.Union ( line[i].x , line[i].y ) ;
			if ( tmp ) {
				++ cnt ;
				ans += line[i].val ;
				addedge ( line[i].x , line[i].y , line[i].val ) ;//新增樹邊
				if ( cnt == n - 1 )//已經得到所有樹邊,退出
					break ;
			}
		}
		return ans ;
	}//kruskal
	
	void dfs ( int u , int p ) {//得到有根樹
		EDGE ( i , u ) {
			int v = edge[i].v ;
			if ( v == p ) continue ;
			fa[v] = u ;//v的父親是u
			deep[v] = deep[u] + 1 ;
			cost[v] = edge[i].w ;
			dfs ( v , u ) ;
		}
	}//dfs
	
	void preProcess () {//預處理出anc和maxcost陣列
		REPF ( i , 1 , n ) {
			anc[i][0] = fa[i] ;// i^0 級祖先就是父親
			maxcost[i][0] = cost[i] ;//i與fa[i]之間的最大權值就是cost[i]
			LOGF ( j , 1 , n )
				anc[i][j] = -1 ;
		}
		LOGF ( j , 1 , n )
			REPF ( i , 1 , n )
				if ( ~anc[i][j - 1] ) {
					int a = anc[i][j - 1] ;
					anc[i][j] = anc[a][j - 1] ;
					maxcost[i][j] = max ( maxcost[i][j - 1] , maxcost[a][j - 1] ) ;
					//選擇i~anc[i][j - 1]中的最大權值和anc[i][j - 1]~anc[anc[i][j - 1][j - 1]中的最大權值
					//也就是i ~ (i^(j-1)) 和 (i^(j-1)) ~ i^j 中選取最大權值(子段的最大權值已經求出)
				}
	}//preProcess
	
	int query ( int p , int q ) {//查詢兩點間的最小瓶頸路
		int tmp , log = 0 , ans = -OO ;
		if ( deep[p] < deep[q] )//令p的深度大於等於q,不滿足就交換
			swap ( p , q ) ;
		LOGF ( i , 1 , deep[p] + 1 )//得到p的最大log段( 滿足 ( 1 << log ) <= deep[p] , 1 << ( log + 1 ) > deep[p] )
			++ log ;
		REPV ( i , log , 0 )//將p的深度降低到與q相同,同時求出p到q深度之間的最大權值
			if ( deep[p] - ( 1 << i ) >= deep[q] ) {//第2^i級祖先的深度大於等於q
				ans = max ( ans , maxcost[p][i] ) ;
				p = anc[p][i] ;//跳到2^i級祖先的位置
			}
		if ( p == q )//q是p的祖先,則之前的處理直接讓p下降到q的位置,p、q之間的最大權值已經求出
			return ans ;//LCA返回p( p 等於 q )
		REPV ( i , log , 0 )//比較的前提是p、q深度相同
			if ( ~anc[p][i] && anc[p][i] != anc[q][i] ) {
				//p和q深度相同,判斷一個即可
				//同時祖先不能是同一個,保證所比較的都是唯一路徑上的邊,否則會跳出最近公共祖先,得到錯誤結果
				ans = max ( ans , maxcost[p][i] ) ;
				ans = max ( ans , maxcost[q][i] ) ;
				p = anc[p][i] ;//跳
				q = anc[q][i] ;//跳
			}
		ans = max ( ans , cost[p] ) ;
		ans = max ( ans , cost[q] ) ;
		return ans ;//LCA返回fa[p]( 它也等於fa[q] )
	}//query
} ;

MST tree ;

void work () {
	int q , u , v , flag = 0 ;
	while ( ~scanf ( "%d%d" , &tree.n , &tree.m ) ) {
		if ( flag )
			puts ( "" ) ;
		flag = 1 ;
		tree.init () ;
		REP ( i , tree.m )
			tree.line[i].input () ;
		tree.kruskal () ;//求最小生成樹
		tree.dfs ( 1 , 0 ) ;//無根樹轉有根樹
		tree.preProcess () ;//倍增查詢預處理
		scanf ( "%d" , &q ) ;
		while ( q -- ) {
			scanf ( "%d%d" , &u , &v ) ;
			printf ( "%d\n" , tree.query ( u , v ) ) ;
		}
	}
}

int main () {
	work () ;
	return 0 ;
}