bzoj1726: [Usaco2006 Nov]Roadblocks第二短路
【题目大意】
求无向图点1到n的次短路。
【思路】
堆优化Dijkstra,方法就是一边跑Dijsktra一边就把次短路径保存下来。和一般Dijkstra不同的是把vis数组去掉了,因为还要生成次短路径。
先来谈谈Dijkstra的优化。对于每次寻找到当前为访问过的点中距离最短的那一个,运用优先队列进行优化,避免全部扫描,每更新一个点的最短距离就加入优先队列。有人会问,一个点如果已经处理完成了,那它还留在队列中怎么办?我们放入队列时将一个点那时的顶点编号和最短距离进行打包,如果取出该点时,它当前的最短距离小于该点标记的最短距离,说明该点已经取到最短距离,不进行操作。或者直接用一个vis数组来记录某一个点是否已经取到最短距离;其次的优化是用邻接表存储与每一个点相连的所有边,方便处理。
这道题的做法和最短路径基本一致,唯一的不同点在于,在求出最短路径的情况下必须要保留下次短路径。对于Dijkstra判断中取出的每一个点,如果到它的最短距离大于当前该点的次短距离,则当前该点已经取到最短距离和次短距离,不进行操作,否则进行两次判断:如果小于最短边,则赋给最短变,并将最短边赋给次短边;或者如果大于最短变且小于次短边,则赋给次短边。两次完成之后均要加入队列。要注意几点:
(1)由于是一张无向图,读入的时候必须正向、逆向分为两条边存储,所以实际有向边的数量为r的两倍,数组绝对不能开小!我就因为这个错拿了90分,还检查了三个小时后找到测试数据才意识到的...
(2)初始化时,源点的短边初始化为0,源点的次短边必须初始化为INF,而不是0。比如下面这组数据:
4 2 1 2 100 2 4 200
答案应该是500,然而如果初始化为0则答案会输出700。因为500的结果是又1到2,在从2返回1,再到2,再到4,100+100+100+200=500得到的;如果次短边初始化为0,则次短路径不再返回源点,而是在2与4之间折返,会偏大。
(3)53行绝对不能直接赋值,而是要swap!因为最短边被修改后,它的值是要留给次短边的。
补充
(1)要注意if (head.len>secondis[head.num]) continue;的位置!我两次写的时候都把它放在了while(k!=-1)里面然后判断(d>secondis[v[k]])这是不对的!因为如果这个时候continue,k的值就无法改变,会导致死循环。
(2)要注意优先队列默认是大顶堆,struct里面设置的时候前后大于小于号必须要相反才能设置成小顶堆!
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<cstdlib> using namespace std; const int INF=0x7fffffff; const int MAXN=100000+10; struct Rec { int num,len; bool operator < (const Rec &a) const { return len>a.len; /*优先队列强制设为以len为关键词的小顶堆*/ } }; int u[MAXN*2],v[MAXN*2],w[MAXN*2]; /*依次表示每条道路的起点、终点和长度*/ int dis[MAXN/2],secondis[MAXN/2]; /*记录通往每一个路口的最短和次短距离*/ int first[MAXN/2],next[MAXN*2]; int n,r; void dijkstra() { priority_queue<Rec> que; for (int i=1;i<n;i++) { dis[i]=INF; secondis[i]=INF; } dis[0]=0; secondis[0]=INF; Rec temp; temp.len=0;temp.num=0; que.push(temp); while (!que.empty()) { Rec head=que.top(); que.pop(); if (head.len>secondis[head.num]) continue; int k=first[head.num]; while (k!=-1) { int d=head.len+w[k]; if (dis[v[k]]>d) { swap(dis[v[k]],d); temp.len=dis[v[k]];temp.num=v[k]; que.push(temp); } if (dis[v[k]]<d && secondis[v[k]]>d) { secondis[v[k]]=d; temp.len=secondis[v[k]];temp.num=v[k]; que.push(temp); } k=next[k]; } } } int main() { scanf("%d%d",&n,&r); memset(first,-1,sizeof(first)); for (int i=0;i<r;i++) { scanf("%d%d%d",&u[i],&v[i],&w[i]); u[i]--; v[i]--; next[i]=first[u[i]]; first[u[i]]=i; v[i+r]=u[i]; u[i+r]=v[i]; w[i+r]=w[i]; next[i+r]=first[u[i+r]]; first[u[i+r]]=i+r; } dijkstra(); cout<<secondis[n-1]<<endl; return 0; }
重新写用的是SPFA。正反跑两次SPFA,然后枚举每一条边,如果起点到一个端点的最短路+另一个端点到终点的最短路+长度 ≠ 最短路,则和答案比较,保存最小值。还是很好理解的
#include<bits/stdc++.h> using namespace std; const int MAXR=1e5+5; const int MAXN=5e3+5; const int INF=21e8; struct edge { int to,len; }; vector<edge> E[MAXN]; int n,r,dis[MAXN],inque[MAXN],dis1[MAXN],dis2[MAXN]; int u[MAXR],v[MAXR],w[MAXR]; void addedge(int u,int v,int w) { E[u].push_back((edge){v,w}); E[v].push_back((edge){u,w}); } void spfa(int S,int T) { for (int i=1;i<=n;i++) inque[i]=0,dis[i]=INF; queue<int> que; que.push(S); inque[S]=1;dis[S]=0; while (!que.empty()) { int head=que.front();que.pop(); inque[head]=0; for (int i=0;i<E[head].size();i++) { int nowdis=dis[head]+E[head][i].len,to=E[head][i].to; if (nowdis<dis[to]) { dis[to]=nowdis; if (!inque[to]) { que.push(to); inque[to]=1; } } } } } int main() { scanf("%d%d",&n,&r); for (int i=1;i<=r;i++) { scanf("%d%d%d",&u[i],&v[i],&w[i]); addedge(u[i],v[i],w[i]); } spfa(1,n); for (int i=1;i<=n;i++) dis1[i]=dis[i]; spfa(n,1); for (int i=1;i<=n;i++) dis2 [i]=dis[i]; int mx=dis1[n],ans=INF; for (int i=1;i<=r;i++) { int now=dis1[u[i]]+dis2[v[i]]+w[i]; if (now!=mx) ans=min(ans,now); now=dis1[v[i]]+dis2[u[i]]+w[i]; if (now!=mx) ans=min(ans,now); } printf("%d",ans); return 0; }