AcWing 178 第K短路

题目描述:

给定一张N个点(编号1,2…N),M条边的有向图,求从起点S到终点T的第K短路的长度,路径允许重复经过点或边。

注意: 每条最短路中至少要包含一条边。

输入格式

第一行包含两个整数N和M。

接下来M行,每行包含三个整数A,B和L,表示点A与点B之间存在有向边,且边长为L。

最后一行包含三个整数S,T和K,分别表示起点S,终点T和第K短路。

输出格式

输出占一行,包含一个整数,表示第K短路的长度,如果第K短路不存在,则输出“-1”。

数据范围

1≤S,T≤N≤1000,
0≤M≤10^5,
1≤K≤1000,
1≤L≤100

输入样例:

2 2
1 2 5
2 1 4
1 2 2

输出样例:

14

分析:

在上一题八数码问题中,我们详细分析了A*算法的原理和实现。本题同样是A*算法的应用,题目中求的第K短路,要求每条路径至少要包含一条边,所以当起点与终点重合时,要去掉本身这条长度为0的自环边,执行k++操作即可。另外第k短路这里的k可以是重复的排名,比如k = 3时,那么第二短的路径有4条时,求的就是第2短的路径长度了。

在上一题中,我们证明了A*算法中终点第一次出队时的距离就是最短路径距离,那么是不是终点第k次出队时的距离就是第k短路的距离呢,答案是肯定的,设终点t第二次出队时到起点的距离是g,到终点的估计距离是0(因为要不大于实际距离),队列中任一个顶点离起点的距离是g1,到终点的估计距离是f,因为终点出队了,所以g < g1 + f,又f小于到终点的实际距离d,故g < g1 + d = 经过这个顶点从起点到终点路径的长度,这就证明了当终点t第二次出队时路径长度已经是所有能到达终点路径中最小的了(因为终点已经出队过一次了),同理可以证明t第k次出队时的距离就是第k短路距离。因为要求的倍数最短路径长度,所以松弛操作时应该将每次的距离都更新下,不论是否变小,每次被更新时到起点的距离可以直接作为队列的一个属性存入优先级队列。

下面要考虑如何涉及估价函数,使得每个点的估计距离要小于等于到终点的实际距离,只需要从终点t倒着求一遍dijkstra算法,就可以求出每个点到终点的最短距离了,后面A*算法不论求的是第几短距离到终点的实际距离都不会小于最短距离。题目中的图是有向图,所以不仅需要建立邻接表,还要建立逆邻接表方便倒着来一遍dijkstra。实现细节并不复杂,具体见代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N = 1005,M = 200005;
typedef pair<int,int> PII;
typedef pair<int,pair<int,int> > PIII;
priority_queue<PII,vector<PII>,greater<PII> > q1;
priority_queue<PIII,vector<PIII>,greater<PIII> > q2;
int d[N],cnt[N];//到终点距离,出队次数
bool st[N];
int k,n,m,s,t,h[N],rh[N],e[M],ne[M],w[M],idx;
void add(int *L,int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=L[a],L[a]=idx++;
}
void dijkstra(){
    memset(d,0x3f,sizeof d);
    d[t] = 0;
    q1.push({0,t});
    while(q1.size()){
        int u = q1.top().second;
        q1.pop();
        if(st[u])   continue;
        st[u] = true;
        for(int i = rh[u];~i;i = ne[i]){
            int j = e[i];
            if(d[u] + w[i] < d[j]){
                d[j] = d[u] + w[i];
                q1.push({d[j],j});
            }
        }
    }
}
int astar(){
    q2.push({d[s],{0,s}});
    while(q2.size()){
        PIII t1 = q2.top();
        q2.pop();
        int dis = t1.second.first,u = t1.second.second;
        cnt[u]++;
        if(u == t && cnt[u] == k)   return dis;
        for(int i = h[u];~i;i = ne[i]){
            int j = e[i];
            q2.push({dis + w[i] + d[j],{dis + w[i],j}});
        }
    }
    return -1;
}
int main(){
    scanf("%d%d",&n,&m);
    int a,b,c;
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    for(int i = 0;i < m;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(h,a,b,c),add(rh,b,a,c);
    }
    scanf("%d%d%d",&s,&t,&k);
    if(s == t)  k++;
    dijkstra();
    printf("%d\n",astar());
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值