A* 求 第 k 短路

55 篇文章 0 订阅
46 篇文章 0 订阅

ACM-ICPC 2018 沈阳赛区网络预赛 D. Made In Heaven

第 k 短路


想一下 BFS 遍历图,如果没有 vis 数组的限制,也就是说找到终点之后继续 BFS 下去,那么终点第 k 次入队,就是第 k 短路。但是如果直接BFS搜索下去,时间复杂度会非常高,因此我们需要剪枝,怎么剪枝呢?

A* 算法就可很好的剪枝,首先 A* 时基于 BFS,只不过对于队列来说有了一个优先级, BFS 算法只是无脑扩展,但是 A* 用到了一个优先队列,每次出队的结点肯定是优先级最高的结点。每次只需要取出每次到达终点最有希望的路径

怎么定义这个优先级呢?设 f [ i ] = g [ i ] + h [ i ] f[i] = g[i] + h[i] f[i]=g[i]+h[i] g [ i ] g[i] g[i] 代表到达 i i i 结点的代价 ,这里就代表走到 i i i 结点的路径长度。

h [ i ] h[i] h[i] 是一个估价函数,这个要根据不同的题自己设计,代表当前结点 i i i 到终点的代价。显而易见, f [ i ] f[i] f[i] 即代表经过 i i i 结点能到达终点的总代价。

在这个题里面怎么设计估价函数呢,一个结点到达终点的代价不就是他们之间的最短路吗,可是要把每个一结点到达终点的最短路都求出来吗? 做法是建反向图,求终点到各个点的最短路径 d i s [ N ] dis[N] dis[N]

最后总结一下流程,
首先建反向图用 SPFA 跑出来终点到各个点的最短路 dis[N]。
用 f[i] 作为优先队列的关键字来进行 A*。
这里 h[i] = dis[i];

题目

存在第 k 短路径,且距离小于 T 就可以,否则不行

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define d(x) cout << (x) << endl
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
const int M = 1e4 + 10;

int n, m, S, E, K, T;

struct node{
    int to, w, ne;
} e[M], re[M];  // 反向边跑 SPFA, 正向边跑 A*

int h[N], rh[N];
int idx;

void init(){
    idx = 0;
    memset(h, -1, sizeof(h));
    memset(rh, -1, sizeof(rh));
}

inline void add(int x, int y, int w){
    e[idx].to = y, e[idx].w = w, e[idx].ne = h[x], h[x] = idx;    // 正向
    re[idx].to = x, re[idx].w = w, re[idx].ne = rh[y], rh[y] = idx++; // 反向
}

int dis[N], vis[N];

// 求出终点到各点的最短长度 dis[i]
// 用这个长度来作为后面 A* 算法的启发式函数 h[i] 
void spfa(){
    memset(dis, INF, sizeof(dis));
    dis[E] = 0;         // 从终点开始
    queue<int> q;
    q.push(E);
    vis[E] = 1;
    while(!q.empty()){
        int cnt = q.front();
        q.pop();
        vis[cnt] = 0;       // 遍历 cnt 的所有边
        for (int i = rh[cnt]; ~i; i = re[i].ne){    
            if(dis[re[i].to] > dis[cnt] + re[i].w){
                dis[re[i].to] = dis[cnt] + re[i].w;
                if(!vis[re[i].to]){
                    vis[re[i].to] = 1;
                    q.push(re[i].to);
                }
            }
        }
    }
}   

struct A{   // 优先队列默认弹出大的,所以这样重载
    int id, g, f;   // id 是点的号
    // 核心式子 : f = g + h,这里 h(i) = dis[i];
    bool operator < (const A& b) const {
        if(f == b.f)
            return g > b.g;
        return f > b.f;
    }
};

int Astar(){
    int res = 0;
    if(S == E)  //判断相同点算不算最短路
        K++;
    if(dis[S] == INF)
        return -1;                  // 起点终点没有通路
    priority_queue<A> q;            //优先队列 默认教大的值先出队列
    q.push(A{S, 0, 0 + dis[S]});
    while(!q.empty()){
        A cnt = q.top();
        q.pop();
        if(cnt.id == E){
            res++;
            if(res == K)    // 第 k 次到达就是结果
                return cnt.g;
        }
        for(int i = h[cnt.id]; ~i; i = e[i].ne){
            int a = e[i].to;               // 估计代价
            int b = cnt.g + e[i].w;         // 实际代价
            int c = b + dis[a];
            q.push(A{a, b, c});
        }
    }
    return -1;  // 没有第 k 短路
}

int main()
{
    while(~scanf("%d%d", &n, &m)){
        init();
        scanf("%d%d%d%d", &S, &E, &K, &T);
        for (int i = 1, x, y, w; i <= m; i++){
            scanf("%d%d%d", &x, &y, &w);
            add(x, y, w);
        }
        spfa();
        int ans = Astar();
        // cout << ans << endl;
        if(ans != -1 && ans <= T)
            printf("yareyaredawa\n");
        else
            printf("Whitesnake!\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值