hdu 1874 畅通工程续 最短路

来源:互联网 发布:知柏地黄丸主治什么 编辑:程序博客网 时间:2024/04/26 14:34

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1874

题意:已知起点和终点,求两点之间的最短路。

解题方案:可以用多种最短路算法,这里讨论一下Dijkstra算法。

传统Dijkstra算法

#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#include <cmath>#include <vector>#include <queue>#include <stack>#include <set>#include <map>using namespace std;#define FOR(i,k,n) for(int i=k;i<n;i++)#define FORR(i,k,n) for(int i=k;i<=n;i++)#define scan(a) scanf("%d",&a)#define scann(a,b) scanf("%d%d",&a,&b)#define scannn(a,b,c) scanf("%d%d%d",&a,&b,&c)#define mst(a,n)  memset(a,n,sizeof(a))#define ll long long#define N 205#define mod 1000000007#define INF 0x3f3f3f3fconst double eps=1e-8;const double pi=acos(-1.0);int mp[N][N];int dist[N];int vis[N];int n,m;void Dijkstra(int v0){    for(int i=0;i<n;i++)        dist[i]=INF,vis[i]=0;    dist[v0]=0;    vis[v0]=1;    for(int i=0;i<n;i++)        if(mp[v0][i]!=INF&&i!=v0)            dist[i]=mp[v0][i];    for(int i=0;i<n-1;i++)    {        int Min=INF,u;        for(int j=0;j<n;j++)        {            if(!vis[j]&&dist[j]<Min)            {                Min=dist[j];                u=j;            }        }        vis[u]=1;        for(int j=0;j<n;j++)        {            if(!vis[j]&&mp[u][j]!=INF&&dist[u]+mp[u][j]<dist[j])                dist[j]=dist[u]+mp[u][j];        }    }}int main(){    //freopen("in.txt","r",stdin);    //freopen("out.txt","w",stdout);    while(scann(n,m)!=EOF)    {        for(int i=0;i<n;i++)            for(int j=0;j<n;j++)                mp[i][j]=INF;        int a,b,w;        for(int i=0;i<m;i++)        {            scanf("%d%d%d",&a,&b,&w);            mp[a][b]=min(mp[a][b],w);            //mp[b][a]=mp[a][b];        }        int s,e;        scann(s,e);        Dijkstra(s);        if(dist[e]!=INF)            printf("%d\n",dist[e]);        else            printf("-1\n");    }    return 0;}

下面是Dijkstra算法的几个关键点

一.最短路径的最优子结构性质(转载 原文链接http://www.cnblogs.com/dolphin0520/archive/2011/08/26/2155202.html)

    该性质描述为:如果P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,k和s是这条路径上的一个中间顶点,那么P(k,s)必定是从k到s的最短路径。下面证明该性质的正确性。

    假设P(i,j)={Vi....Vk..Vs...Vj}是从顶点i到j的最短路径,则有P(i,j)=P(i,k)+P(k,s)+P(s,j)。而P(k,s)不是从k到s的最短距离,那么必定存在另一条从k到s的最短路径P'(k,s),那么P'(i,j)=P(i,k)+P'(k,s)+P(s,j)<P(i,j)。则与P(i,j)是从i到j的最短路径相矛盾。因此该性质得证。

二.vis[i]代表是否已确定找到了从起点到i的最短路径

三.在n-1次循环中去找除了起点以外的n-1个点的最短路径,每一次循环确定一个,那为什么每次在所有还未确定最短路径的点中距离最短的,就一定是已经找到了从起点到该点的最短路径的点呢?


    反证法:1.在第一次循环中,假设在所有vis为false的点中dist[V1]最短,即起点V0到V1的距离最短,从起点到其他点(如V5)再到V1,V0V5>V0V1,那V0V5V1就更大于V0V1,则dist[V1]已经为最短路径,vis[V1]可以置为true;
                 2.同理可得,在某一次循环中,在所有vis为false的点中dist[V2]最短,如果绕过其他点如V3到V2距离更短,即dist[V3]+V3V2<dist[V2],则dist[v3]<dist[V2],这与dist[V2]最短相矛盾。换句话说,dist[V3]就已经比dist[V2]大了,  从V0绕过V3再到V2的距离肯定比dist[V2]要长,所以此时dist[V2]就是从V0到V2的最短路径。

四.为什么每次循环中只拿当前确定最短路径的点去更新?

    准确的说应该是拿当前确定的最短路径这一段从起点到该点的最优路径,去更新其它还未确定最短路径的点到起点的距离(松弛操作)。因为是滚动比较的,这一次比较就是和历史最优的记录进行比较。

五.Dijkstra算法处理负权有向图可能会出错

比如下图


    用Dijkstra求得d[1,2]=3,事实上d[1,2]=2。这是因为Dijkstra算法每次将离起点最近的点视为是已经找到最短路的点,并拿它去松弛其他还未找到最短路的点,而不再更新自己的最短路。这就是为什么Dijkstra算法要快一点,因为它每次只拿当前新增的已找到最短路的点去松弛其他点,相对SPFA来说减少了很多冗余的松弛操作。但这也就是为什么Dijkstra算法不能处理负权有向图,原因如上图所示,此时离起点最近的点不一定就是已经找到最短路的点,这个点还有可能可以被负权更新成更短的路径。


    对于负权有向图可以使用SPFA来处理。SPFA虽然有些冗余:只要被松弛了而且不在队列里面就进队列,因为此时这个被松弛了的点可能可以松弛其他点,只要有可能就将其进队列,待以后出队列的时候就尝试松弛其他所有和这个点有边相连的点。虽然SPFA比Dijkstra慢了点,但可以正确地处理有边权为负的情况(此时一定为有向图,若为无向图且有负权边肯定形成了负环,有负环就可以一直在负环里转,此时无解)


用SPFA算法解决负权有向图最短路问题(参考qsc大神直播的代码,视频链接,温馨提醒:视频里有些小错误,qsc大神已经在评论区纠正了)

#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#include <cmath>#include <vector>#include <queue>#include <stack>#include <set>#include <map>using namespace std;#define FOR(i,k,n) for(int i=k;i<n;i++)#define FORR(i,k,n) for(int i=k;i<=n;i++)#define scan(a) scanf("%d",&a)#define scann(a,b) scanf("%d%d",&a,&b)#define scannn(a,b,c) scanf("%d%d%d",&a,&b,&c)#define mst(a,n)  memset(a,n,sizeof(a))#define ll long long#define N 200005#define mod 1000000007#define INF 0x3f3f3f3fconst double eps=1e-8;const double pi=acos(-1.0);vector<pair<int,int> > E[N];int d[N];int inque[N];int main(){    //freopen("in.txt","r",stdin);    //freopen("out.txt","w",stdout);    int n,m;    while(cin>>n>>m)    {        mst(d,INF);        mst(inque,0);        FOR(i,0,N) E[i].clear();        FOR(i,0,m)        {            int a,b,c;            cin>>a>>b>>c;            E[a].push_back(make_pair(b,c));            //E[b].push_back(make_pair(a,c));        }        queue<int> q;        int s=1;        q.push(s); inque[s]=1; d[s]=0;        while(!q.empty())        {            int cur=q.front();            q.pop(); inque[cur]=0;            //printf("**当前出队列点:%d\n",cur);            for(int i=0;i<E[cur].size();i++)            {                int v=E[cur][i].first;                //printf("松弛%d\n",v);                if(d[v]>d[cur]+E[cur][i].second)                {                    d[v]=d[cur]+E[cur][i].second;                    if(!inque[v]) q.push(v),inque[v]=1;                }            }        }        FORR(i,2,n) printf("%d\n",d[i]);    }    return 0;}

用优先队列(堆)优化的Dijkstra算法,按qcs大神的这种写法,好像也可以解决负权有向图最短路问题?(参考qsc大神直播的代码,视频链接,温馨提醒:视频里有些小错误,qsc大神已经在评论区纠正了)

#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <string>#include <cmath>#include <vector>#include <queue>#include <stack>#include <set>#include <map>using namespace std;#define FOR(i,k,n) for(int i=k;i<n;i++)#define FORR(i,k,n) for(int i=k;i<=n;i++)#define scan(a) scanf("%d",&a)#define scann(a,b) scanf("%d%d",&a,&b)#define scannn(a,b,c) scanf("%d%d%d",&a,&b,&c)#define mst(a,n)  memset(a,n,sizeof(a))#define ll long long#define N 200005#define mod 1000000007#define INF 0x3f3f3f3fconst double eps=1e-8;const double pi=acos(-1.0);vector<pair<int,int> > E[N];int d[N];int main(){    //freopen("in.txt","r",stdin);    //freopen("out.txt","w",stdout);    int n,m;    while(cin>>n>>m)    {        mst(d,INF);        FOR(i,0,N) E[i].clear();        FOR(i,0,m)        {            int a,b,c;            cin>>a>>b>>c;            E[a].push_back(make_pair(b,c));            //E[b].push_back(make_pair(a,c));        }        priority_queue<pair<int,int> > q;        int s=1;        d[s]=0;        q.push(make_pair(-d[s],s));        while(!q.empty())        {            int cur=q.top().second;            q.pop();            //if(cur==t) break;            for(int i=0;i<E[cur].size();i++)            {                int v=E[cur][i].first;                if(d[v]>d[cur]+E[cur][i].second)                {                    d[v]=d[cur]+E[cur][i].second;                    q.push(make_pair(-d[v],v));                }            }        }        FORR(i,2,n) printf("%d\n",d[i]);    }    return 0;}







0 0