上一篇博客我稍微讲了一下k短路,这次,我来讲一下最短路以及次短路计数。要求:能熟练打出普通最短路。
还是用一道题来引入这个知识点:POJ3463 Sightseeing
题目大意:给定一个有向图,求最短路以及最短路长度加1的路径条数。
这道题让求最短路计数也就罢了,但是我们还要对最短路+1的路径计数,这可怎么办呢?计数让我们联想到了
dp,但是如果按照往常在dp的话,这张图必须是DAG,但没有任何地方说了这个图是DAG,并且因为有权值,
我们不能够进行缩点,所以DAG上dp的这条路就被封死了。
所以该怎么办,我们可以在dijkstra或SPFA上直接dp吗?答案是可以,因为每次都会由一个点扩展下一个点,
这样就可以做到在两个点之间dp转移,但是要如何转移呢?如果是仅仅最短路计数的话就会十分容易,可以
用一个dp[i]来记录s到i的路径长度为dis[i]时的路径条数。如果最短路被更新了,直接dp[i]=dp[la]即可,没
有更新且这条路径与最短路相等的话,将dp[i]加等于dp[la]即可。但是如果将这个扩展到次短路该怎么办呢?
就用两个数组即可,但为了方便,我们用dis[i][0/1]和dp[i][0/1]分别表示从s到i点的 最短路/次短路 长度和从
s到i点的 最短路/次短路 路径条数。
而转移就是这样的:
1.如果小于最短路:原先最短路变为次短路,次短路条数变为原先最短路条数,然后更新最短路及其条数。
2.如果等于最短路:直接累计最短路条数即可。
3.如果小于次短路且大于最短路:直接更新次短路及其条数
4.如果等于次短路:累计次短路条数。
注意:
1.我接下来要展示的代码用了万能头,但poj里理论上是不让用万能头的
2.算完了以后不要忘记判断最短路长度是否是次短路-1
3.一定不要忘记这道题是多组数据,而且要记得初始化
4.这是一个有向图不是无向图
5.一定要记得排除重复,也就是代码中的vis的作用。
代码:
#include<bits/stdc++.h> using namespace std; const int NR=1e3+5; const int MR=2e4+5; int read() { int x=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();} return x*f; } struct Edge//邻接表结构体 { int to,next,w; }e[MR]; int head[NR],cnt; void add(int a,int b,int c)//邻接表存图 { cnt++; e[cnt].to=b; e[cnt].w=c; e[cnt].next=head[a]; head[a]=cnt; } int T,m,n; int dis[NR][2]; int dp[NR][2]; bool vis[NR][2]; struct Nd//dijkstra必须的结构体 { int dis,id,num; bool operator <(const Nd A) const//优先队列以什么优先 { return dis>A.dis;//注意一定要是>号因为priority_queue默认是大根堆 } }; void Init()//多组数据不要忘记Init { memset(head,0,sizeof(head)); cnt=1; } void dij(int s,int t)//dijkstra要记录起终点 { priority_queue <Nd> q; //优先队列 memset(dis,0x3f,sizeof(dis));//初始化极大值 memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); Nd tmp;tmp.dis=0,tmp.id=s,tmp.num=0; q.push(tmp);dis[s][0]=0,dp[s][0]=1;//赋初值 while(!q.empty()) { int x=q.top().id,num=q.top().num;q.pop();//将dis最小的取出,并记录当前是最短路还是次短路 if(vis[x][num]) continue;//如果这个点这个的最短/次短遍历过,直接跳过 vis[x][num]=1;//vis记录信息 for(int i=head[x];i;i=e[i].next)//枚举下一个点 { int y=e[i].to,cur=dis[x][num]+e[i].w;//记录下一个点,最短路长度。 if(cur<dis[y][0])//第一种情况 { dis[y][1]=dis[y][0]; dp[y][1]=dp[y][0];//原先的最短路变为次短路 dis[y][0]=cur; dp[y][0]=dp[x][num];//更新最短路长度及其条数 tmp.dis=dis[y][0],tmp.id=y,tmp.num=0;q.push(tmp);//将这个点这种状态push进优先队列 tmp.dis=dis[y][1],tmp.id=y,tmp.num=1;q.push(tmp);//将这个点这种状态push进优先队列 continue; } if(cur==dis[y][0]) //第二种情况 { dp[y][0]+=dp[x][num];//累计最短路条数 continue; } if(cur<dis[y][1]) //第三种情况 { dis[y][1]=cur; dp[y][1]=dp[x][num]; //更新次短路 tmp.dis=dis[y][1],tmp.id=y,tmp.num=1;q.push(tmp); //将这个点这种状态push进优先队列 continue; } if(cur==dis[y][1]) //第四种情况 { dp[y][1]+=dp[x][num];//累计次短路条数 continue; } } } } int main() { T=read();//多组数据的数量 while(T--) { Init(); n=read(),m=read(); for(int i=1;i<=m;i++) { int x=read(),y=read(),z=read(); add(x,y,z); //有向图建图 } int s=read(),t=read(); dij(s,t); int ans=dp[t][0]; if(dis[t][1]-1==dis[t][0]) ans+=dp[t][1];//如果次短路是最短路+1,ans累计上dp[t][1] printf("%d ",ans); } return 0; }