图论2-次短路计数

上一篇博客我稍微讲了一下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\n",ans); 
    }
    return 0;
}

 

posted @ 2020-03-24 13:36  CZD648  阅读(296)  评论(0编辑  收藏  举报
Live2D