本文涉及知识
单源最短路 迪氏优先 图论知识汇总
LeetCode2203. 得到要求路径的最小带权子图
给你一个整数 n ,它表示一个 带权有向 图的节点数,节点编号为 0 到 n - 1 。
同时给你一个二维整数数组 edges ,其中 edges[i] = [fromi, toi, weighti] ,表示从 fromi 到 toi 有一条边权为 weighti 的 有向 边。
最后,给你三个 互不相同 的整数 src1 ,src2 和 dest ,表示图中三个不同的点。
请你从图中选出一个 边权和最小 的子图,使得从 src1 和 src2 出发,在这个子图中,都 可以 到达 dest 。如果这样的子图不存在,请返回 -1 。
子图 中的点和边都应该属于原图的一部分。子图的边权和定义为它所包含的所有边的权值之和。
示例 1:
输入:n = 6, edges = [[0,2,2],[0,5,6],[1,0,3],[1,4,5],[2,1,1],[2,3,3],[2,3,4],[3,4,2],[4,5,1]], src1 = 0, src2 = 1, dest = 5
输出:9
解释:
上图为输入的图。
蓝色边为最优子图之一。
注意,子图 [[1,0,3],[0,5,6]] 也能得到最优解,但无法在满足所有限制的前提下,得到更优解。
示例 2:
输入:n = 3, edges = [[0,1,1],[2,1,1]], src1 = 0, src2 = 1, dest = 2
输出:-1
解释:
上图为输入的图。
可以看到,不存在从节点 1 到节点 2 的路径,所以不存在任何子图满足所有限制。
提示:
3 <= n <= 105
0 <= edges.length <= 105
edges[i].length == 3
0 <= fromi, toi, src1, src2, dest <= n - 1
fromi != toi
src1 ,src2 和 dest 两两不同。
1 <= weight[i] <= 105
迪氏最短路
正确性证明
s r c 1 → d e s t s r c 2 → d e s t src1\rightarrow dest \quad src2\rightarrow dest src1→destsrc2→dest 一定存在交点c,我们枚举第一个交点c。然后计算:
d i s ( s r c 1 → c ) + d i s ( s r c 1 → c ) + d i s ( c → d e s t ) dis(src1 \rightarrow c)+dis(src1 \rightarrow c)+dis(c \rightarrow dest) dis(src1→c)+dis(src1→c)+dis(c→dest)的最小值
如果c不可能是第一个交点,会不会带来错误?
一,c和src1、src2、dest不连通。我们用llNotMay = 2e11表示不连通。如果其和大于等于此值,则表示不连通,忽略。
二,c不是第一个交点,令第一个交点是c1,则c会被c1方案淘汰。 c 1 → c c1\rightarrow c c1→c 在c方案中被计算了两次,在c1方案只计算了一次。故c1方案一定优于c方案。
综上所述:不存在的方案,必定被淘汰或忽略。
解题思路
本题是稀疏图,故用迪氏优化求最短路。
先求src1和src2的单源最短路。
再将有向边反转。
求dest的单源最短路。
枚举c ,求最小值。
代码
核心代码
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>> Grid(int rCount, int cCount, std::function<bool(int, int)> funVilidCur, std::function<bool(int, int)> funVilidNext)
{
vector<vector<int>> vNeiBo(rCount * cCount);
auto Move = [&](int preR, int preC, int r, int c)
{
if ((r < 0) || (r >= rCount))
{
return;
}
if ((c < 0) || (c >= cCount))
{
return;
}
if (funVilidCur(preR, preC) && funVilidNext(r, c))
{
vNeiBo[cCount * preR + preC].emplace_back(r * cCount + c);
}
};
for (int r = 0; r < rCount; r++)
{
for (int c = 0; c < cCount; c++)
{
Move(r, c, r + 1, c);
Move(r, c, r - 1, c);
Move(r, c, r, c + 1);
Move(r, c, r, c - 1);
}
}
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;
}
};
//堆(优先队列)优化迪杰斯特拉算法 狄克斯特拉(Dijkstra)算法详解
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;
};
class Solution {
public:
long long minimumWeight(int n, vector<vector<int>>& edges, int src1, int src2, int dest) {
auto neiBo0 = CNeiBo::Three(n, edges, true, 0);
auto edges1 = edges;
for (auto& v : edges1) {
std::swap(v[0], v[1]);
}
auto neiBo1 = CNeiBo::Three(n, edges1, true, 0);
CHeapDis dis1(n), dis2(n), dis3(n);
dis1.Cal(src1, neiBo0);
dis2.Cal(src2, neiBo0);
dis3.Cal(dest, neiBo1);
long long llRet = LLONG_MAX;
for (int i = 0; i < n; i++) {
long long cur = dis1.m_vDis[i] + dis2.m_vDis[i] + dis3.m_vDis[i];
llRet = min(llRet, cur);
}
return (llRet >= dis1.m_llEmpty) ? -1 : llRet;
}
};
单元测试
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
Assert::AreEqual(t1, t2);
}
void AssertEx( double t1, double t2)
{
auto str = std::to_wstring(t1) + std::wstring(1,32) + std::to_wstring(t2);
Assert::IsTrue(abs(t1 - t2) < 1e-5,str.c_str() );
}
template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
Assert::AreEqual(v1.size(), v2.size());
for (int i = 0; i < v1.size(); i++)
{
Assert::AreEqual(v1[i], v2[i]);
}
}
template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
sort(vv1.begin(), vv1.end());
sort(vv2.begin(), vv2.end());
Assert::AreEqual(vv1.size(), vv2.size());
for (int i = 0; i < vv1.size(); i++)
{
AssertEx(vv1[i], vv2[i]);
}
}
namespace UnitTest
{
int n;
vector<vector<int>> edges;
int src1, src2, dest;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod00)
{
n = 6, edges = { {0,2,2},{0,5,6},{1,0,3},{1,4,5},{2,1,1},{2,3,3},{2,3,4},{3,4,2},{4,5,1} }, src1 = 0, src2 = 1, dest = 5;
auto res = Solution().minimumWeight(n, edges, src1, src2, dest);
AssertEx(9LL, res);
}
TEST_METHOD(TestMethod01)
{
n = 3, edges = { {0,1,1},{2,1,1} }, src1 = 0, src2 = 1, dest = 2;
auto res = Solution().minimumWeight(n, edges, src1, src2, dest);
AssertEx(-1LL, res);
}
};
}
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。