1. 程式人生 > >【POJ】3463 Sightseeing 最短路+比最短路大一的路(最短路 or 最短路+DP)

【POJ】3463 Sightseeing 最短路+比最短路大一的路(最短路 or 最短路+DP)

BAPC 2006 Qualification

題型:求最短路以及比最短路長度大一的次短路,並要求計數。

傳送門:【POJ】3463 Sightseeing

題目大意:給你n個點m條邊的有向圖(unidirectional是有向!),讓你求最短路以及長度比最短路大一的路的數量。題目保證數量不超過10^9。(2 ≤ N ≤ 1,000 and 1 ≤ M ≤ 10,000)

題目分析:
如果直接在Dijkstra演算法中求最短路的數量以及次短路的數量的話,那麼演算法中就有四種情況存在:
1.比最短路短
2.和最短路一樣短
3.比次短路短
4.和次短路一樣長
都要考慮到,且要注意細節。
每次可以在最短路上增廣或者次短路上增廣,最短路上增廣包括所有四種情況,次短路增廣包括後兩種情況(次短路得到的路肯定不可能比最短路短),那麼判斷是否標號永久化就需要對同一個結點永久化兩次,一次是最短路,一次是次短路。

程式碼如下:

#include <stdio.h>
#include <string.h>
#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 REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define clear( A , X ) memset ( A , X , sizeof A )

typedef int type_d ;
const int maxN = 1005 ;
const int maxQ = 100005 ;
const int maxH = 100005 ;
const int maxE = 100005 ;
const int oo = 0x3f3f3f3f ;

struct Heap {
	int num ;
	int kind ;
	type_d d ;
	Heap () {}
	Heap ( type_d D , int Num , int Kind ) :
		d(D) , num(Num) , kind(Kind) {}
} ;

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

struct Priority_Queue {
	Heap heap[maxH] ;
	int top ;
	
	void swap ( Heap &a , Heap &b ) {
		Heap  tmp ;
		tmp =   a ;
		a   =   b ; 
		b   = tmp ;
	}
	
	int cmp ( const Heap a , const Heap b ) {
		return a.d < b.d ;
	}
	
	void Clear () {
		top = 1 ;//下標從1開始
	}
	
	int Empty () {
		return 1 == top ;
	}
	
	void Push ( type_d d , int num , int kind ) {
		heap[top] = Heap ( d , num , kind ) ;
		int o = top ++ ;
		while ( o > 1 && cmp ( heap[o] , heap[o >> 1] ) ) {
			swap ( heap[o] , heap[o >> 1] ) ;
			o >>= 1 ;
		}
	}
	
	int Front () {
		return heap[1].num ;
	}
	
	int Kind () {
		return heap[1].kind ;
	}
	
	void Pop () {
		heap[1] = heap[-- top] ;
		int o = 1 , p = o , l = o << 1 , r = o << 1 | 1 ;
		while ( 1 ) {
			if ( l < top && cmp ( heap[l] , heap[p] ) )
				p = l ;
			if ( r < top && cmp ( heap[r] , heap[p] ) )
				p = r ;
			if ( p == o )
				break ;
			swap ( heap[o] , heap[p] ) ;
			o = p , l = o << 1 , r = o << 1 | 1 ;
		}
	}
} ;

struct Dij {
	Priority_Queue Q ;
	Edge edge[maxE] ;
	int adj[maxN] , cntE ;
	int done[2][maxN] ;
	type_d d[2][maxN] ;
	//int p[maxN] ;
	int cnt[2][maxN] ;
		
	void addedge ( int u , int v , int w = 0 ) {
		edge[cntE] = Edge ( v , w , adj[u] ) ;
		adj[u] = cntE ++ ;
		//edge[cntE] = Edge ( u , w , adj[v] ) ;
		//adj[v] = cntE ++ ;
	}
	
	void Init () {
		cntE = 0 ;
		Q.Clear () ;
		clear ( d , oo ) ;
		clear ( cnt , 0 ) ;
		clear ( adj , -1 ) ;
		//clear ( p , -1 ) ;
	}
	
	void Dijkstra ( int s ) {
		clear ( done , 0 ) ;
		d[0][s] = 0 ;
		cnt[0][s] = 1 ;
		Q.Push ( d[0][s] , s , 0 ) ;
		while ( !Q.Empty () ) {
			int u = Q.Front () ;
			int kind = Q.Kind () ;
			Q.Pop () ;
			if ( done[kind][u] )
				continue ;
			done[kind][u] = 1 ;
			for ( int i = adj[u] ; ~i ; i = edge[i].n ) {
				int v = edge[i].v ;
				int val = d[kind][u] + edge[i].w ;
				if ( d[0][v] > val ) {
					d[1][v] = d[0][v] ;
					cnt[1][v] = cnt[0][v] ;
					Q.Push ( d[1][v] , v , 1 ) ;
					d[0][v] = val ;
					cnt[0][v] = cnt[kind][u] ;
					Q.Push ( d[0][v] , v , 0 ) ;
				}
				else if ( d[0][v] == val )
					cnt[0][v] += cnt[kind][u] ;
				else if ( d[1][v] > val ) {
					d[1][v] = val ;
					cnt[1][v] = cnt[kind][u] ;
					Q.Push ( d[1][v] , v , 1 ) ;
				}
				else if ( d[1][v] == val )
					cnt[1][v] += cnt[kind][u] ;
			}
		}
	}
} ;

Dij D ;

void work () {
	int n , m ;
	int s , t ;
	int u , v , w ;
	D.Init () ;
	scanf ( "%d%d" , &n , &m ) ;
	REP ( i , m ) {
		scanf ( "%d%d%d" , &u , &v , &w ) ;
		D.addedge ( u , v , w ) ;
	}
	scanf ( "%d%d" , &s , &t ) ;
	D.Dijkstra ( s ) ;
	int ans = D.cnt[0][t] ;
	//printf ( "%d %d\n" , D.d[0][t] , D.d[1][t] ) ;
	if ( D.d[1][t] == D.d[0][t] + 1 )
		ans += D.cnt[1][t] ;
	printf ( "%d\n" , ans ) ;
}

int main () {
	int T ;
	scanf ( "%d" , &T ) ;
	while ( T -- )
		work () ;
	return 0 ;
}

然後是另一種方法:
Dijkstra依舊求出最短路的數量,然後用DP在反圖上倒著記憶化搜尋,如果d[u] == d[v] + w[v,u],說明到目前為止還在最短路上,那麼可以在繼續遞迴中得到次短路的數量cnt1,則屬於該結點的次短路數量cnt += cnt1。如果d[u] == d[v] + w[v,u] - 1,說明正在次短路的一條邊上,後面一定都是最短路的邊了,則下面直接得到最短路數量cnt2,則次短路數量cnt += cnt2。感覺比上面的方法還要簡單~

程式碼如下:

#include <stdio.h>
#include <string.h>
#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 REP( i , n ) for ( int i = 0 ; i < n ; ++ i )
#define clear( A , X ) memset ( A , X , sizeof A )

typedef int type_d ;
const int maxN = 1005 ;
const int maxQ = 100005 ;
const int maxH = 100005 ;
const int maxE = 100005 ;
const int oo = 0x3f3f3f3f ;

struct Heap {
	int num ;
	type_d d ;
	Heap () {}
	Heap ( type_d D , int Num ) :
		d(D) , num(Num) {}
} ;

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

struct Priority_Queue {
	Heap heap[maxH] ;
	int top ;
	
	void swap ( Heap &a , Heap &b ) {
		Heap  tmp ;
		tmp =   a ;
		a   =   b ; 
		b   = tmp ;
	}
	
	int cmp ( const Heap a , const Heap b ) {
		return a.d < b.d ;
	}
	
	void Clear () {
		top = 1 ;//下標從1開始
	}
	
	int Empty () {
		return 1 == top ;
	}
	
	void Push ( type_d d , int num ) {
		heap[top] = Heap ( d , num ) ;
		int o = top ++ ;
		while ( o > 1 && cmp ( heap[o] , heap[o >> 1] ) ) {
			swap ( heap[o] , heap[o >> 1] ) ;
			o >>= 1 ;
		}
	}
	
	int Front () {
		return heap[1].num ;
	}
	
	void Pop () {
		heap[1] = heap[-- top] ;
		int o = 1 , p = o , l = o << 1 , r = o << 1 | 1 ;
		while ( 1 ) {
			if ( l < top && cmp ( heap[l] , heap[p] ) )
				p = l ;
			if ( r < top && cmp ( heap[r] , heap[p] ) )
				p = r ;
			if ( p == o )
				break ;
			swap ( heap[o] , heap[p] ) ;
			o = p , l = o << 1 , r = o << 1 | 1 ;
		}
	}
} ;

struct Dij {
	Priority_Queue Q ;
	Edge edge[maxE] , E[maxE] ;
	int adj[maxN] , Adj[maxN] , cntE , cntEE ;
	int done[maxN] ;
	type_d d[maxN] ;
	//int p[maxN] ;
	int cnt[maxN] ;//最短路數量
	int cnt2[maxN] ;//次短路數量
		
	void addedge ( int u , int v , int w = 0 ) {
		edge[cntE] = Edge ( v , w , adj[u] ) ;
		adj[u] = cntE ++ ;
		E[cntEE] = Edge ( u , w , Adj[v] ) ;
		Adj[v] = cntEE ++ ;
	}
	
	void Init () {
		cntE = 0 ;
		cntEE = 0 ;
		Q.Clear () ;
		clear ( d , oo ) ;
		clear ( cnt , 0 ) ;
		clear ( cnt2 , -1 ) ;
		clear ( adj , -1 ) ;
		clear ( Adj , -1 ) ;
		//clear ( p , -1 ) ;
	}
	
	void Dijkstra ( int s ) {
		clear ( done , 0 ) ;
		d[s] = 0 ;
		cnt[s] = 1 ;
		Q.Push ( d[s] , s ) ;
		while ( !Q.Empty () ) {
			int u = Q.Front () ;
			Q.Pop () ;
			if ( done[u] )
				continue ;
			done[u] = 1 ;
			for ( int i = adj[u] ; ~i ; i = edge[i].n ) {
				int v = edge[i].v ;
				int val = d[u] + edge[i].w ;
				if ( d[v] > val ) {
					d[v] = val ;
					cnt[v] = cnt[u] ;
					Q.Push ( d[v] , v ) ;
				}
				else if ( d[v] == val )
					cnt[v] += cnt[u] ;
			}
		}
	}
	
	int dp ( int u ) {
		if ( cnt2[u] != -1 )
			return cnt2[u] ;
		cnt2[u] = 0 ;
		for ( int i = Adj[u] ; ~i ; i = E[i].n ) {
			int v = E[i].v ;
			if ( d[u] == d[v] + E[i].w )
				cnt2[u] += dp ( v ) ;
			else if ( d[u] == d[v] + E[i].w - 1 )
				cnt2[u] += cnt[v] ;
		}
		return cnt2[u] ;
	}
} ;

Dij D ;

void work () {
	int n , m ;
	int s , t ;
	int u , v , w ;
	D.Init () ;
	scanf ( "%d%d" , &n , &m ) ;
	REP ( i , m ) {
		scanf ( "%d%d%d" , &u , &v , &w ) ;
		D.addedge ( u , v , w ) ;
	}
	scanf ( "%d%d" , &s , &t ) ;
	D.Dijkstra ( s ) ;
	//printf ( "%d\n" , D.cnt[t] ) ;
	printf ( "%d\n" , D.cnt[t] + D.dp ( t ) ) ;
}

int main () {
	int T ;
	scanf ( "%d" , &T ) ;
	while ( T -- )
		work () ;
	return 0 ;
}