每周一算法:单源次短路

题目描述

观光

“您的个人假期”旅行社组织了一次比荷卢经济联盟的巴士之旅。

比荷卢经济联盟有很多公交线路。每天公共汽车都会从一座城市开往另一座城市。沿途汽车可能会在一些城市(零或更多)停靠。

旅行社计划旅途从 S S S 城市出发,到 F F F 城市结束。由于不同旅客的景点偏好不同,所以为了迎合更多旅客,旅行社将为客户提供多种不同线路。游客可以选择的行进路线有所限制,要么满足所选路线总路程为 S S S F F F 的最小路程,要么满足所选路线总路程仅比最小路程多一个单位长度。

在这里插入图片描述
如上图所示,如果 S = 1 S=1 S=1 F = 5 F=5 F=5,则这里有两条最短路线 1 → 2 → 5 , 1 → 3 → 5 1→2→5,1→3→5 125,135,长度为 6 6 6
;有一条比最短路程多一个单位长度的路线 1 → 3 → 4 → 5 1→3→4→5 1345,长度为 7 7 7

现在给定比荷卢经济联盟的公交路线图以及两个城市 S S S F F F,请你求出旅行社最多可以为旅客提供多少种不同的满足限制条件的线路。

输入格式

第一行包含整数 T T T,表示共有 T T T 组测试数据。

每组数据第一行包含两个整数 N N N M M M,分别表示总城市数量和道路数量。

接下来 M M M 行,每行包含三个整数 A , B , L A,B,L A,B,L,表示有一条线路从城市 A A A 通往城市 B B B,长度为 L L L

需注意,线路是单向的,存在从 A A A B B B 的线路不代表一定存在从 B B B A A A 的线路,另外从城市 A A A 到城市 B B B 可能存在多个不同的线路。

接下来一行,包含两个整数 S S S F F F,数据保证 S S S F F F 不同,并且 S S S F F F 之间至少存在一条线路。

输出格式

每组数据输出一个结果,每个结果占一行。

数据保证结果不超过 1 0 9 10^9 109

样例 #1

样例输入 #1

2
5 8
1 2 3
1 3 2
1 4 5
2 3 1
2 5 3
3 4 2
3 5 4
4 5 3
1 5
5 6
2 3 1
3 2 1
3 1 10
4 5 2
5 2 7
5 2 7
4 1

样例输出 #1

3
2

提示

【数据范围】

2 ≤ N ≤ 1000 2≤N≤1000 2N1000,
1 ≤ M ≤ 10000 1≤M≤10000 1M10000,
1 ≤ L ≤ 1000 1≤L≤1000 1L1000
1 ≤ A , B , S , F ≤ N 1≤A,B,S,F≤N 1A,B,S,FN

算法思想

根据题目描述,求的是从 S S S 城市出发、到 F F F 城市结束的所有路线中,最短路、或者仅比最小路程多 1 1 1个单位长度的次短路的方案数。

在博主的另一篇文章每周一算法:最短路计数介绍过,可以使用动态规划的思想,定义状态 f [ i ] f[i] f[i]表示起点到顶点 i i i的最短路的方案数。为了能同时求出次短路的方案数,可以采用拆点的思想:

  • f [ i ] [ 0 ] f[i][0] f[i][0]表示起点到顶点 i i i的最短路的方案数
  • f [ i ] [ 1 ] f[i][1] f[i][1]表示起点到顶点 i i i的次短路的方案数

与最短路计数一样,图中有可能存在环,不一定存在拓扑序列,因此不能通过循环迭代直接计算状态;可以使用最短路算法,在计算最短路的同时将状态计算出来。同时,在计算最短路时,能够找到一个拓扑序来计算状态。这样就需要最短路算法能够构造出一个最短路拓扑图,如下图所示:
在这里插入图片描述
由于边权不相同,这里可以用Dijkstra算法求最短路。Dijkstra算法计算最短路时,每个点只会出队 1 1 1次,当一个点出队时,是不会更新前面已经出队的点到起点的最短路,因此出队序列满足拓扑序。

算法流程

  • 将起点 S S S的最短路初始化为 0 0 0,即 d i s [ S ] [ 0 ] = 0 dis[S][0]=0 dis[S][0]=0;方案数初始化为 1 1 1,即 f [ S ] [ 0 ] = 1 f[S][0] = 1 f[S][0]=1
  • 在求解最短路的过程中
    • v v v点到起点的最短路能够被点松弛:
      • 将之前的最短路变为次短路,更新次短路的方案数
      • 更新最短路的长度,设置最短路的方案数
    • v v v点到起点的最短路等于经过 u u u点中转的距离,累加最短路的方案数
    • v v v点到起点的次短路能够被点松弛:
      • 更新次短路的长度,设置次短路的方案数
    • v v v点到起点的次短路等于经过 u u u点中转的距离,累加次短路的方案数
  • 最后,如果次短路的长度 + 1 +1 +1等于最短路长度,即 d i s [ F ] [ 1 ] = d i s [ F ] [ 0 ] + 1 dis[F][1] = dis[F][0] + 1 dis[F][1]=dis[F][0]+1,那么总方案数为 f [ F ] [ 0 ] + f [ F ] [ 1 ] f[F][0]+f[F][1] f[F][0]+f[F][1];否则,总方案数为 f [ F ] [ 0 ] f[F][0] f[F][0]

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 1005, M = 1e4 + 5;
int h[N], e[M], w[M], ne[M], idx; 
int n, m, S, F;
int dis[N][2], f[N][2]; //f[i][0]表示到i点最短路条数
bool st[N][2];
struct V {
    int ver, type, dis; //节点、类型(0最短路、1次短路)、到起点距离
    bool operator > (const V &v) const //小顶堆,重载 > 
    {
        return dis > v.dis;
    }
};
void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dijkstra()
{
    memset(st, 0, sizeof st);
    memset(dis, 0x3f, sizeof dis);
    memset(f, 0, sizeof f);
    dis[S][0] = 0, f[S][0] = 1; //初始化最短路和条数
    priority_queue<V, vector<V>, greater<V>> q; //小顶堆
    q.push({S, 0, 0}); //起点最短路入堆
    while(q.size())
    {
        V t = q.top(); q.pop();
        int u = t.ver, type = t.type, d = t.dis, cnt = f[u][type];
        if(st[u][type]) continue;
        st[u][type] = true;
        
        for(int i = h[u]; ~ i; i = ne[i])
        {
            int v = e[i];
            if(dis[v][0] > d + w[i]) //可以更新最短路
            {
                dis[v][1] = dis[v][0], f[v][1] = f[v][0]; //最短路变次短路,同时更新次短路个数
                q.push({v, 1, dis[v][1]}); //次短路入堆
                dis[v][0] = d + w[i], f[v][0] = cnt; //更新最短路以及最短路个数
                q.push({v, 0, dis[v][0]});
            }
            else if(dis[v][0] == d + w[i]) //与最短路相等
            {
                f[v][0] += cnt; //累加最短路个数
            }
            else if(dis[v][1] > d + w[i]) //可以更新次短路
            {
                dis[v][1] = d + w[i], f[v][1] = cnt;
                q.push({v, 1, dis[v][1]});
            }
            else if(dis[v][1] == d + w[i]) //与次短路相等
            {
                f[v][1] += cnt;
            }
        }
    }
    int ans = f[F][0]; //最短路个数
    if(dis[F][1] == dis[F][0] + 1) //次短路等于最短路+1
        ans += f[F][1];
    return ans;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T --)
    {
        scanf("%d%d", &n, &m);
        memset(h, -1, sizeof h);
        idx = 0;
        while(m --)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
        }
        scanf("%d%d", &S, &F);
        printf("%d\n", dijkstra());
    }
    return 0;
}
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值