实名制认证网站seo是搜索引擎营销
本文涉及知识点
C++图论
分支界限法
LeetCode1786. 从第一个节点出发到最后一个节点的受限路径数
现有一个加权无向连通图。给你一个正整数 n ,表示图中有 n 个节点,并按从 1 到 n 给节点编号;另给你一个数组 edges ,其中每个 edges[i] = [ui, vi, weighti] 表示存在一条位于节点 ui 和 vi 之间的边,这条边的权重为 weighti 。
从节点 start 出发到节点 end 的路径是一个形如 [z0, z1, z2, …, zk] 的节点序列,满足 z0 = start 、zk = end 且在所有符合 0 <= i <= k-1 的节点 zi 和 zi+1 之间存在一条边。
路径的距离定义为这条路径上所有边的权重总和。用 distanceToLastNode(x) 表示节点 n 和 x 之间路径的最短距离。受限路径 为满足 distanceToLastNode(zi) > distanceToLastNode(zi+1) 的一条路径,其中 0 <= i <= k-1 。
返回从节点 1 出发到节点 n 的 受限路径数 。由于数字可能很大,请返回对 109 + 7 取余 的结果。
示例 1:
输入:n = 5, edges = [[1,2,3],[1,3,3],[2,3,1],[1,4,2],[5,2,2],[3,5,1],[5,4,10]]
输出:3
解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。三条受限路径分别是:
- 1 --> 2 --> 5
- 1 --> 2 --> 3 --> 5
- 1 --> 3 --> 5
示例 2:
输入:n = 7, edges = [[1,3,1],[4,1,2],[7,3,4],[2,5,3],[5,6,1],[6,7,2],[7,5,3],[2,6,4]]
输出:1
解释:每个圆包含黑色的节点编号和蓝色的 distanceToLastNode 值。唯一一条受限路径是:1 --> 3 --> 7 。
提示:
1 <= n <= 2 * 104
n - 1 <= edges.length <= 4 * 104
edges[i].length == 3
1 <= ui, vi <= n
ui != vi
1 <= weighti <= 105
任意两个节点之间至多存在一条边
任意两个节点之间至少存在一条路径
图论+BFS(错误解法)
本图是稀疏图,故用迪氏堆优化求各点到n的距离。节点i到n的最小距离简记为d[i]。
任意路径都是d[i]严格降序,故不会有环。
ans[i]记录1到i的方案数。
BFS的状态表示:当前节点cur,初始为1。
BFS的后续状态:next是cur的临接点,且d[next] >d[cur]。
BFS去重:vis数组。
BFS返回值:ans.back
时间复杂度:O(mlogn+m) m是边数。
错误原因:无法保证无后续性。
错误代码
class Solution {public:int countRestrictedPaths(int n, vector<vector<int>>& edges) {auto neiBo = CNeiBo::Three(n, edges, false, 1);CHeapDis hd(n);hd.Cal(n - 1, neiBo);vector<C1097Int<>> cnt(n);cnt[0] = 1;vector<bool> vis(n);queue<int> que;auto Add = [&](int cur) {if (vis[cur])return;vis[cur] = true;que.emplace(cur);};Add(0);while (que.size()) {const auto cur = que.front();que.pop();for (const auto& nxt : neiBo[cur]) {if (hd.m_vDis[nxt.first] >= hd.m_vDis[cur]) { continue; }cnt[nxt.first] += cnt[cur];Add(nxt.first);}}return cnt.back().ToInt();}};
分支界限法
将各节点从1开始改成0从开始。
小根堆记录{d[i],i},初始{d[n-1],n-1}。 从堆顶取出元素i,枚举i的临界点j,如果d[j] <= dp[i]忽略。如果vis[j]忽略,将{d[j],j}放到堆中。按此顺序处理i,可以保证无后效性,下面来证明:
处理i时,任何d[j] < d[i]的i都已经处理。令j的最短路径是{j1,j2 ⋯ \cdots ⋯ j} 这些节点的最短路径显然都小于d[i]。用数学归纳法来证明。
j1是n-1的临接点,所以j1一定在堆,由于d[j1] <d[i],故j1先于i处理。处理j1时,j2也入堆,由于d[j2] < d[i],故j2先于i处理。 ⋯ \cdots ⋯
进一步优化
vis在出堆时判断,入堆到出堆有时间差,可能这段时间vis[i]发生变化。这样优化后,就是迪氏堆优化最短路。vSort的节点,确保d[i]升序。这样可以保证无后效性。
代码
核心代码
class CNeiBo
{
public: static vector<vector<int>> Two(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0) {vector<vector<int>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);}}return vNeiBo;} static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0){vector<vector<std::pair<int, int>>> vNeiBo(n);for (const auto& v : edges){vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);if (!bDirect){vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);}}return vNeiBo;} static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat){vector<vector<int>> neiBo(neiBoMat.size());for (int i = 0; i < neiBoMat.size(); i++){for (int j = i + 1; j < neiBoMat.size(); j++){if (neiBoMat[i][j]){neiBo[i].emplace_back(j);neiBo[j].emplace_back(i);}}}return neiBo;}
};typedef pair<long long, int> PAIRLLI;
class CHeapDis
{
public:CHeapDis(int n,long long llEmpty = LLONG_MAX/10):m_llEmpty(llEmpty){m_vDis.assign(n, m_llEmpty);}void Cal(int start, const vector<vector<pair<int, int>>>& vNeiB){std::priority_queue<PAIRLLI, vector<PAIRLLI>, greater<PAIRLLI>> minHeap;minHeap.emplace(0, start);while (minHeap.size()){const long long llDist = minHeap.top().first;const int iCur = minHeap.top().second;minHeap.pop();if (m_llEmpty != m_vDis[iCur]){continue;}m_vDis[iCur] = llDist;for (const auto& it : vNeiB[iCur]){minHeap.emplace(llDist + it.second, it.first);}}}vector<long long> m_vDis;const long long m_llEmpty;
};
template<int MOD = 1000000007>
class C1097Int
{
public:C1097Int(long long llData = 0) :m_iData(llData% MOD){}C1097Int operator+(const C1097Int& o)const{return C1097Int(((long long)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((long long)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = (m_iData + MOD - o.m_iData) % MOD;return *this;}C1097Int operator-(const C1097Int& o){return C1097Int((m_iData + MOD - o.m_iData) % MOD);}C1097Int operator*(const C1097Int& o)const{return((long long)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((long long)m_iData * o.m_iData) % MOD;return *this;}C1097Int operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this /= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(long long n)const{C1097Int iRet = 1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}int ToInt()const{return m_iData;}
private:int m_iData = 0;;
};class Solution {public:int countRestrictedPaths(int n, vector<vector<int>>& edges) {auto neiBo = CNeiBo::Three(n, edges, false, 1); vector<int> vSortNode,dis(n,-1);priority_queue<pair<int,int>,vector<pair<int, int>>,greater<>> heap;heap.emplace(0, n - 1);while (heap.size()) {const auto [d, cur] = heap.top();heap.pop();if (-1 != dis[cur]) { continue; }dis[cur] =d ;vSortNode.emplace_back(cur);for (const auto& pre : neiBo[cur]) {heap.emplace(d + pre.second, pre.first);}}vector<C1097Int<>> cnt(n);cnt[n - 1] = 1;for (int i : vSortNode) {for (const auto& pre : neiBo[i]) {if (dis[pre.first] > dis[i]) {cnt[pre.first] += cnt[i];}}}return cnt[0].ToInt();}};
单元测试
int n;vector<vector<int>> edges;TEST_METHOD(TestMethod1){n = 1, edges = { };auto res = Solution().countRestrictedPaths(n, edges);AssertEx(1, res);}TEST_METHOD(TestMethod2){n = 2, edges = { {1,2,3} };auto res = Solution().countRestrictedPaths(n, edges);AssertEx(1, res);}TEST_METHOD(TestMethod11){n = 5, edges = { {1,2,3},{1,3,3},{2,3,1},{1,4,2},{5,2,2},{3,5,1},{5,4,10} };auto res = Solution().countRestrictedPaths(n, edges);AssertEx(3, res);}TEST_METHOD(TestMethod12){n = 7, edges = { {1,3,1},{4,1,2},{7,3,4},{2,5,3},{5,6,1},{6,7,2},{7,5,3},{2,6,4} };auto res = Solution().countRestrictedPaths(n, edges);AssertEx(1, res);}TEST_METHOD(TestMethod13){n = 9, edges = { {6,2,35129},{3,4,99499},{2,7,43547},{8,1,78671},{2,1,66308},{9,6,33462},{5,1,48249},{2,3,44414},{6,7,44602},{1,7,14931},{8,9,38171},{4,5,30827},{3,9,79166},{4,8,93731},{5,9,64068},{7,5,17741},{6,3,76017},{9,4,72244} };auto res = Solution().countRestrictedPaths(n, edges);AssertEx(6, res);}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。