[POJ2449] Remmarguts' Date(k 短路 / A*)

传送门:Remmarguts' Date

题意为求源点 s 到 终点 t 的第 k 短路,可以采用启发式搜索 A* 算法来求解。

A* 算法的核心为估值函数 f(x) = g(x)+h(x) ,其中 f(x) 为当前某个点的估值,g(x) 为开始搜索时到当前点的代价,h(x) 为当前点到目标点的代价的估值,h(x) 必须小于等于实际值。而在本题中,g(x) 为到达当前点时已走的路径长度,h(x) 为当前点到目标点的最短路径长度。

具体的求解方法是,首先利用 SPFA 求解反向最短路。估值函数中的 h(x) 采用了当前点到目标点的最短路径长度,由于题目给定的图是有向图,所以可以在建图时同时反向建图(即建立一个所有边方向相反权重不变的图),然后从终点 t 开始做一次单源最短路,求出 t 到其他点的最短路长。这样,h(x) 的值就确定下来了。

然后就可以进行 A* 算法。A* 算法类似 BFS的优化(实际上,可以通过改变估值函数,使得 A* 算法退化为 BFS),它利用一个优先队列维护 f(x),使得估值 f(x) 越小,则越早出队拓展。出队的点与 BFS 一样,访问与该点有边相连且为边指向的点,更新状态(估值函数),加入优先队列。当优先队列出队的点为 t 时,若为 t 第一次出现,说明这是 s 到 k 的最短路;若为第二次出现,则说明这是第 2 短路...以此类推,当 t 第 k 次出队时,求出的便是 k 短路了。

另外,需要注意的是,若 s == t,需要执行一个 ++k 的操作...因为作为起点的 t 会多出队一次。

#include <iostream>
#include <queue>
#include <vector>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1024;
const int maxm = 1e5+5;
struct Edge
{
    int to, w, next;
    Edge() {}
    Edge(int to, int w, int next): to(to), w(w), next(next) {}
};
struct Node
{
    int x, g, h;
    Node() {}
    Node(int x, int g, int h): x(x), g(g), h(h) {}
    bool operator < (const Node& n) const //注意这里的符号别写反...否则会无限MLE...
    {
        if((g+h) != (n.g+n.h))
            return (g+h) > (n.g+n.h);
        return g > n.g;
    }
};
int n, m, s, t, k;
Edge edge1[maxm], edge2[maxm];
int head1[maxn], head2[maxn];
int dis[maxn], vis[maxn];

void init()
{
    for(int i = 0; i < maxn; ++i)
        head1[i] = head2[i] = -1;
}

void addEdge(Edge edge[], int head[], int u, int v, int w, int id)
{
    edge[id].w = w;
    edge[id].to = v;
    edge[id].next = head[u];
    head[u] = id;
}

void read()
{
    cin >> n >> m;
    int u, v, w;
    for(int i = 0; i < m; ++i)
    {
        cin >> u >> v >> w;
        addEdge(edge1, head1, u, v, w, i);
        addEdge(edge2, head2, v, u, w, i);
    }
    cin >> s >> t >> k;
}

void spfa(int ss)
{
    for(int i = 0; i < maxn; ++i)
        dis[i] = INF;
    dis[ss] = 0;

    queue<int> q;
    q.push(ss);
    vis[ss] = 1;

    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u]= 0;

        for(int i = head2[u]; ~i; i = edge2[i].next)
        {
            Edge v = edge2[i];
            if(dis[v.to] > dis[u]+v.w)
            {
                dis[v.to] = dis[u]+v.w;
                if(!vis[v.to])
                {
                    q.push(v.to);
                    vis[v.to] = 1;;
                }
            }
        }
    }
}

int AStar()
{
    if(dis[s] == INF)
        return -1;
    if(s == t) //划重点
        ++k;
    priority_queue<Node> q;
    q.push(Node(s, 0, dis[s]));

    int cnt = 0;
    while(!q.empty())
    {
        Node cur = q.top();
        q.pop();
        if(cur.x == t)
            if(++cnt == k)
                return cur.g;

        for(int i = head1[cur.x]; ~i; i = edge1[i].next)
        {
            Edge next = edge1[i];
            q.push(Node(next.to, cur.g+next.w, dis[next.to]));
        }
    }
    return -1;
}

void solve()
{
    spfa(t);
    cout << AStar() << endl;
}

int main()
{
    ios::sync_with_stdio(false);
   	cin.tie(0); cout.tie(0);
    init();
    read();
    solve();
    return 0;
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值