http://poj.org/problem?id=2449
题意:求有N个点,M条边的第K短路。 N<=1000 , M<=100000 , K<=1000 。
思路:第K短路。裸的第K短路题。
先说第一种思路:Dijkstra变形,将原来是一维的Dijkstra中的dis[ ]数组变成二维的dis[ i ][ j ],表示结点i 的第j短路。这样就可以由Dij的过程求出最终节点的K短路了。但是时间复杂度确不尽如人意,时间复杂度可以达到:O(K*N*N)=10^9,对于本题来说显然是会超时的,这里还是贴一下这种思路的代码,在K较小的时候还是可以采纳的(比如说次短路)。
代码:
#include<stdio.h> #include<string.h> const long long INF = 9999999999999999ll ; long long N ,M ; long long S,T,K ; long long dis[1010][1010] ; long long v[1010] ; struct Node{ long long d ; int num ; int next ; }edge[100010] ; int root[1010] ,cnt; void swap(long long& a, long long& b){ long long c = a ; a = b ; b = c ; } void Dij(){ for(long long i=1;i<=N;i++){ v[i] = 0 ; for(long long j=0;j<=K;j++){ dis[i][j] = INF ; } } dis[S][0] = 0 ; dis[0][0] = INF ; v[0] = 0 ; while(1){ long long k = 0; for(long long i=1;i<=N;i++){ if(v[i]<K && dis[i][v[i]] < dis[k][v[k]]) k = i ; } if( k==0 ) break ; if(k==T && v[k]==K-1 ) break ; for(int j=root[k] ;j!=-1;j=edge[j].next){ int u = edge[j].num; long long dd = edge[j].d ; if(v[u]<K && dis[k][v[k]]+dd<dis[u][K]){ dis[u][K] = dis[k][v[k]] + dd ; for(long long j=K;j>0;j--){ if(dis[u][j] < dis[u][j-1]){ swap(dis[u][j] , dis[u][j-1]); } } } } v[k] ++ ; } if(dis[T][K-1] == INF) printf("-1\n"); else printf("%lld\n",dis[T][K-1]); } void add(int a, int b, int c){ edge[cnt].num = b ; edge[cnt].d = c ; edge[cnt].next = root[a] ; root[a] = cnt ++ ; } int main(){ long long a, b , c ; while(scanf("%lld %lld",&N,&M) == 2){ cnt = 0 ; memset(root , -1 , sizeof(root)); for(long long i=1;i<=M;i++){ scanf("%lld %lld %lld",&a,&b,&c); add(a,b,c) ; } scanf("%lld %lld %lld",&S,&T,&K); if(S == T) K++ ; Dij() ; } return 0 ; }
首先考虑的是这样,在求第K短路的时候, 我们用BFS搜索,从源点开始扩展,每次设置一个优先队列,选出当前离起点距离最小的结点进行扩展,这个选出的结点就是该结点当时的第i短路(证明比较简单,用Dij的思路证明即可),当一个终点被弹出优先队列K次的时候,该点的K短路也就找到了。但是这样的思路对于N和K较大的时候会超时,甚至也会超内存。这时候我们可以考虑的是在搜索的时候利用一些启发信息来进行搜索,因此我们需要考虑的是每次进行扩展结点时候结点的选择问题,因为是求终点的K短路,我们可以这么想,用g[ i ]表示结点 i 到源点已经走过的距离,我们设置一个估价函数h[ i ]表示i结点到终点还需要的最短距离(这个是由A*启发搜索规定的),这时候的启发函数就是f[ i ] = h [ i ] + g [ i ] ,我们每次从队列中弹出结点的时候,是弹出该估价函数最小的那个结点,这样我们的搜索就带有启发性了。
下面说明为什么这样做会更快。 假设队列中有两个结点i 和 j ,其中f[i] < f[j] ,按照上面的算法,我们应该弹出 i 结点,这是因为i结点达到终点的K短路的可能性更大,为什么这么说呢,因为 f[i] < f[j] 。很可能j是最终结点的大于K短路,所以从理论上来说,我们并没有必须起扩展这样的点,因此我们这里扩展i先是合理的。
代码:
/*
第K短路 + A*搜索
*/
#include<stdio.h>
#include<string.h>
#include<queue>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f ;
int N,M ,S,T, K;
struct Node{
int num ;
int dis ;
int next ;
}edge[100010], redge[100010];
int cnt , recnt ;
int root[1010] , reroot[1010] ;
void Init(){
cnt = recnt = 0 ;
memset(root , -1 , sizeof(root));
memset(reroot, -1 ,sizeof(reroot)) ;
}
void add(int a, int b, int c){
edge[cnt].num = b ;
edge[cnt].dis = c ;
edge[cnt].next = root[a] ;
root[a] = cnt ++ ;
}
void readd(int a ,int b , int c){
redge[recnt].num = b ;
redge[recnt].dis = c ;
redge[recnt].next = reroot[a] ;
reroot[a] = recnt ++ ;
}
int dis[1010] ;
bool vis[1010] ;
void Dij(){
for(int i=1;i<=N;i++){
vis[i] = 0;
dis[i] = INF ;
}
dis[T] = 0 ;
for(int i=1;i<=N;i++){
int _min = INF , min_n ;
for(int j=1;j<=N;j++){
if(vis[j]==0 && dis[j]<_min){
_min = dis[j] ; min_n = j ;
}
}
vis[min_n] = 1 ;
for(int j=reroot[min_n] ;j!=-1;j=redge[j].next){
int u = redge[j].num ;
int d = redge[j].dis ;
if(vis[u]==0 && dis[u]>dis[min_n]+d){
dis[u] = dis[min_n] + d ;
}
}
}
}
struct Node1{
Node1(int a, int b)
:num(a) , d(b) {}
int num ;
int d ;
friend bool operator<(const Node1 &n1 , const Node1& n2) {
int aa = n1.d + dis[n1.num] ;
int bb = n2.d + dis[n2.num];
return aa > bb ;
}
} ;
priority_queue<Node1,vector<Node1>,less<Node1> > que ;
int t[1010] ;
int a_star(){
if(S == T) K++ ;
if(dis[S] == INF) return -1 ;
while(!que.empty()) que.pop() ;
que.push( Node1(S,0) );
memset(t , 0 ,sizeof(t));
while(!que.empty()){
Node1 n2 = que.top() ;
que.pop() ;
int n = n2.num ;
int d = n2.d ;
t[n] ++ ; //找到了n结点的t[n]短路
if(t[T] == K){
return d ;
}
if(t[n] > K)
continue ;
for(int i=root[n];i!=-1;i=edge[i].next){
int v = edge[i].num;
int dd = edge[i].dis ;
que.push(Node1(v,d+dd));
}
}
return -1;
}
int main(){
int a, b, c ;
while(scanf("%d%d",&N,&M) == 2){
Init() ;
for(int i=1;i<=M;i++){
scanf("%d %d %d",&a,&b,&c);
add(a,b,c); readd(b,a,c);
}
scanf("%d%d%d",&S,&T,&K);
Dij();
int ans = a_star() ;
printf("%d\n",ans);
}
return 0 ;
}