第k短路 dijkstra+A* POJ2449 解题报告

第k短路 dijkstra+A* POJ2449 解题报告

题意:
输入格式:
n m
a1 b1 v1
a2 b2 v2

am bm vm
s t k

解释:给定n个点,m条边,每条边从 ai 到 bi ,权值为vi,求以s为起点,到达t的第k短路。注意:如果s==t,则s必须经过一条非空路径到达t。即最短路不能是0。

思路:
① 利用dijkstra计算出终点t到其他点 “逆着走” 的最短距离,并存储在数组 d 上。d[i]表示t到i的最短距离。
比如:s = 4,t = 1第k短路 dijkstra+A* POJ2449 解题报告_第1张图片
那么d[2] = 1,d[3] = 2,这就是逆着走的意思。
② 进行A*搜索,从s出发,进行一个类似于BFS的搜索,每次选出“最短距离”的路径,直到到达点t k次时,说明选出了到达t的第k短路,则返回其路径长度。
最短距离的计算方式为:设当前点为p,则 s到达p的长度 加上 d[p] (t到p的最短路径)即为其比较基准。
比如:

  • 从 4 出发 到 2 时的距离是2,那么“最短距离”应该说2+d[2] = 2+1 = 3
  • 如果说路径是4->3->2->3->2,此时4到2的距离应该是4,那么最短距离应该就是4+d[2] = 4+1 = 5

下面是代码,注释可帮助理解

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
//最大顶点数
const int maxn = 1009;
typedef long long int ll;
//dijkstra得出的"反向"最短距离
ll d[maxn];
//总顶点数
int n;
//Node 用于存储边,和供dijkstra算法使用
struct Node{
	//to就是去哪个点,value就是权值
    ll to,value;
   
    void set(ll a,ll b){
        to = a;
        value = b;
    }
    //权值变相反数,这个函数是为了让最大堆变最小堆
    void re(){
        value*=-1;
    }
};
//为了配合priority_queue而设置的函数
bool operator<(const Node &a,const Node &b){
    return a.value<b.value;
}
vector<Node> pg[maxn];//正向图,存正向边
vector<Node> ng[maxn];//反向图,存反向边,供dijkstra使用
bool vis[maxn];//标记是否访问到了,dijkstra使用
//跑A*用的类
struct Side{
    ll to,value;
    void set(ll a,ll b){
        to = a;
        value = b;
    }
    void re(){
        value*=-1;
    }
};
//注意这里的比较函数发生了改变哦
bool operator<(const Side &a,const Side &b){
    //A*用的函数
    return a.value-d[a.to]<b.value-d[b.to];
}
void init(){
    //-1标识未访问,其实也可以用INF
    for(int i = 0;i<n;++i){
        d[i] = -1;
    }
}
//s是起点
void dijkstra(int s){
    init();
    Node work;
    d[s] = 0;
    work.set(s,0);
    priority_queue<Node>q;
    q.push(work);
    while(!q.empty()){
        work = q.top();
        q.pop();
        //其实这里用了re()没什么用,因为我没用到现在的work.value的实际值
        //但是呢指不定我手贱使用了它,所以预防一下
        work.re();
        ll from = work.to;
        if(vis[from])continue;
        vis[from] = true;
        int len = ng[from].size();
        ll to,value;
        for(int i = 0;i<len;++i){
            to = ng[from][i].to;
            value = ng[from][i].value;
            if(d[from]+value<d[to]||d[to]==-1){
                d[to]  = d[from]+value;
                //特别注意re(),取相反数,最大堆变最小堆
                work.set(to,d[to]);
                work.re();
                q.push(work);
            }
        }
    }
}
ll solve(int s,int t,int k){
    //检查是否不可到达,否则可能在圈里无限跑,导致TLE
    /*这是没了下面这一语句时的无限跑数据:
    3 2
	1 2 1
	2 1 1
	1 3 1
	*/
    if(d[s]==-1)return -1;
    //根据题意,如果s==t,必须经过路径,不能原地到达
    //因为这里0也是算最短路的,所以需要多跑一次
    if(s==t)++k;
    priority_queue<Side> q;
    Side work;
    work.set(s,0);
    q.push(work);
    while(!q.empty()){
        work = q.top();
        q.pop();
        //这里的re()是有必要的
        work.re();
        //值必须先保存,否则work可能被set()更改
        ll from = work.to,value = work.value;
        if(from == t){
            k--;
            //第k次了,返回答案
            if(k==0)return value+d[from];
        }
        int len = pg[from].size();
        while(len--){
            ll to = pg[from][len].to;
            ll Add = pg[from][len].value;
            work.set(to,value+Add);
            //同样的注意re()
            work.re();
            q.push(work);
        }
    }
    return -1;
}
int main(){
    //freopen("in.txt","r",stdin);
    ll m,from,to,s,t,k;
    ll value;
    Node work;
    scanf("%lld %lld",&n,&m);
    while(m--){
    	//注意它的点是1起始,我有点不习惯,所以转换成了0起始
        scanf("%lld %lld %lld",&from,&to,&value);
        work.set(from-1,value);
        //加反向边
        ng[to-1].push_back(work);
        //加正向边
        work.set(to-1,value);	
        pg[from-1].push_back(work);
    }
        scanf("%lld %lld %lld",&s,&t,&k);
    dijkstra(t-1);
    ll ans = solve(s-1,t-1,k);
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:(ACM,图论,第k短路,算法)