(最易懂的解析)从第一个节点出发到最后一个节点的受限路径数(单源最短路径+拓扑排序)

13 篇文章 2 订阅
5 篇文章 0 订阅

题目链接:

1786. 从第一个节点出发到最后一个节点的受限路径数 - 力扣(LeetCode) (leetcode-cn.com)

题意:

给你由n个点构成的m条边(x, y, w){从x到y有一条无向边,长度为w},构成一个连通图,然后让每个点到点n的最短路径距离作为当前点的权值,然后问从点1到点n有几条不同的路径(每个点必须往比自己权值小的点走)。

  1. 1 <= n <= 2 * 104
  2. n - 1 <= edges.length <= 4 * 104
  3. edges[i].length == 3
  4. 1 <= ui, vi <= n
  5. ui != vi
  6. 1 <= weighti <= 105
  7. 任意两个节点之间至多存在一条边
  8. 任意两个节点之间至少存在一条路径

最后答案对1e9+7取模

题解:

Step.1 :通过求单源最短路径确定每个点的权值。

Step.2 :拓扑排序,在排序过程中有几个点需要注意:

1. 计算入度的时候,遍历整个图,如果 x 与 y 之间有路,且,dis[x] > dis[y] , 那么 du[y] ++;, 因为我们要从权值高的点往权值低的点走。

2. 不论点1的度为多少,都要将其放在拓扑排序队列中的队首,因为路径是从点1开始的。

3. 需要初始化在点1的位置的路径个数为 1,这样才能往后递推,如果点1可以通往点2,那么 ans[2]  = (ans[2] + ans[1])%mod ; 推广至点x可以通往点y,那么 ans[x] = (ans[x] + ans[y]) % mod; 

4. 为什么需要拓扑排序往后地推,直接在点1 遍历往后推不可以吗?当然是不可以的,这是因为在推的过程中 如果点 x 通向 点z ,这时候 ans[z] = (ans[x] + ans[z]) % mod , 但是之后还可能会有点通向点x,这时候ans[x] 变化了,但是已经给 ans[z] 赋值了,所以最终答案是不确定的,可能多也可能少路径的个数,但一定是不对的。拓扑排序保证了点都在有序的推进,不会出现“抢跑”的情况。

AC代码:

class Solution{
public:

    typedef pair<int, int> P;
    static const int maxn = 20007;
    const int INF = 0x3f3f3f3f;
    const int mod = 1e9 + 7;

    int dis[maxn], vis[maxn], du[maxn], dp[maxn]; // 这里的dp数组相当于题解中的 ans 数组
    vector<P> G[maxn];

    void spfa(int n){ // 求单源最短路径
        for(int i = 0; i <= n; i++) dis[i] = INF, vis[i] = 0;
        queue<int> que;
        que.push(n);
        dis[n] = 0;
        vis[n] = 1;
        while(!que.empty())
        {
            int curv = que.front(); que.pop();
            vis[curv] = 0;
            for(int i = 0; i < G[curv].size(); i++)
            {
                P t = G[curv][i];
                if(dis[curv] + t.second < dis[t.first])
                {
                    dis[t.first] = dis[curv] + t.second;
                    if(!vis[t.first]) que.push(t.first);
                }
            }
        }
    }

    void topo(int n) // 拓扑排序
    {
        memset(du, 0, sizeof(du));
        for(int i = 1; i <= n; i++)
        {
            for(int j = 0; j < G[i].size(); j++)
            {
                int nxt = G[i][j].first;
                if(dis[i] > dis[nxt]) du[nxt]++; // 求入度
            }
        }
        du[1] = 0;

        queue<int> que;
        que.push(1); // 保证点1在队首
        memset(dp, 0, sizeof(dp));
        dp[1] = 1;
        for(int i = 2; i <= n; i++) if(du[i] == 0) que.push(i); // 这些点也都要,不然可能拓扑排序会中止,有些地方“解不开”
        while(!que.empty())
        {
            int curv = que.front(); que.pop();
            for(int i = 0; i < G[curv].size(); i++)
            {
                int nxt = G[curv][i].first;
                if(dis[curv] > dis[nxt]) 
                {
                    du[nxt] --;
                    dp[nxt] = (dp[nxt] + dp[curv]) % mod; // 递推
                    if(!du[nxt]) que.push(nxt);
                }
            }
        }
    }


    int countRestrictedPaths(int n, vector<vector<int>>& edge){
        for(int i = 0; i < edge.size(); i++)
        {
            G[ edge[i][0] ].push_back( P(edge[i][1], edge[i][2]) );
            G[ edge[i][1] ].push_back( P(edge[i][0], edge[i][2]) ); // 无向图
        }
        spfa(n);
        topo(n);
        return dp[n];
    }
};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值