题意
给定n个地点(1,2,3,…,n)以及m条无向的路径,并说明了这m条路径所需要的时间。此外,任何两个地点可以通过航班直接抵达,所需要的距离为他们的编号差的平方和(u-v)*(u-v)。
在最多坐k次飞机的情况下,求每个从城市1,到其他城市的最短路径。
思路
1.不考虑航班路线的最短路径
不考虑航班路线,我们可以用dijkstra算出1号城市到其他城市的最短路径。
2.考虑+1次航班路线的最短路径
利用第一步算出的dist,
再考虑,只走一次航班的场景,此时对于节点u,它的最短路径为
d
i
s
n
e
w
[
u
]
=
m
i
n
(
d
i
s
o
l
d
[
v
]
+
(
u
−
v
)
∗
(
u
−
v
)
)
dis_{new}[u] = min(dis_{old}[v]+(u-v)*(u-v))
disnew[u]=min(disold[v]+(u−v)∗(u−v))
如何快速查找对应的v,以来确定 d i s n e w [ u ] dis_{new}[u] disnew[u]?我们可以用凸优化,它本质上就是利用了斜率。
对于任意两点v1<v2,u到它们的距离为
d
i
s
o
l
d
[
v
1
]
+
(
u
−
v
1
)
∗
(
u
−
v
1
)
,
d
i
s
o
l
d
[
v
2
]
+
(
u
−
v
2
)
∗
(
u
−
v
2
)
dis_{old}[v1]+(u-v1)*(u-v1),dis_{old}[v2]+(u-v2)*(u-v2)
disold[v1]+(u−v1)∗(u−v1),disold[v2]+(u−v2)∗(u−v2)
如果v2更优,有
d
i
s
o
l
d
[
v
1
]
+
(
u
−
v
1
)
∗
(
u
−
v
1
)
>
d
i
s
o
l
d
[
v
2
]
+
(
u
−
v
2
)
∗
(
u
−
v
2
)
dis_{old}[v1]+(u-v1)*(u-v1) > dis_{old}[v2]+(u-v2)*(u-v2)
disold[v1]+(u−v1)∗(u−v1)>disold[v2]+(u−v2)∗(u−v2)
有
(
d
i
s
o
l
d
[
v
2
]
+
v
2
∗
v
2
)
−
(
d
i
s
o
l
d
[
v
1
]
+
v
1
∗
v
1
)
<
(
2
∗
v
2
−
2
∗
v
1
)
∗
u
(dis_{old}[v2]+v2*v2) - (dis_{old}[v1] + v1*v1) < (2*v2-2*v1)*u
(disold[v2]+v2∗v2)−(disold[v1]+v1∗v1)<(2∗v2−2∗v1)∗u
假设存在第3个节点v3,v3>v2,且v3更优,有
(
d
i
s
o
l
d
[
v
3
]
+
v
3
∗
v
3
)
−
(
d
i
s
o
l
d
[
v
2
]
+
v
2
∗
v
2
)
<
(
2
∗
v
3
−
2
∗
v
2
)
∗
u
(dis_{old}[v3]+v3*v3) - (dis_{old}[v2] + v2*v2) < (2*v3-2*v2)*u
(disold[v3]+v3∗v3)−(disold[v2]+v2∗v2)<(2∗v3−2∗v2)∗u
整理上述两式,有
k
12
=
(
d
i
s
o
l
d
[
v
2
]
+
v
2
∗
v
2
)
−
(
d
i
s
o
l
d
[
v
1
]
+
v
1
∗
v
1
)
(
2
∗
v
2
−
2
∗
v
1
)
<
u
k_{12}=\frac{(dis_{old}[v2]+v2*v2) - (dis_{old}[v1] + v1*v1)}{(2*v2-2*v1)} < u
k12=(2∗v2−2∗v1)(disold[v2]+v2∗v2)−(disold[v1]+v1∗v1)<u
k
23
=
(
d
i
s
o
l
d
[
v
3
]
+
v
3
∗
v
3
)
−
(
d
i
s
o
l
d
[
v
2
]
+
v
2
∗
v
2
)
(
2
∗
v
3
−
2
∗
v
2
)
<
u
k_{23}=\frac{(dis_{old}[v3]+v3*v3) - (dis_{old}[v2] + v2*v2)}{(2*v3-2*v2)} < u
k23=(2∗v3−2∗v2)(disold[v3]+v3∗v3)−(disold[v2]+v2∗v2)<u
这里的k有实际含义,对于任意两个节点x<y,如果 k x y < u k_{xy}<u kxy<u,则y节点优于x节点,到u的距离更小;反之,如果 k x y > u k_{xy}>u kxy>u,则x节点优于y节点,到u的距离更小。
凸优化:
对于任意3个节点x<y<z,如果
k
x
y
>
=
k
y
z
k_{xy}>=k_{yz}
kxy>=kyz,那么此时,最优节点要么为x,要么为z,一定不是y。
证明:
- 当 u > = k x y > = k y z u>=k_{xy}>=k_{yz} u>=kxy>=kyz时,此时y优于x,z优于y。
- 当 k x y > = u > = k y z k_{xy}>=u>=k_{yz} kxy>=u>=kyz时,此时x优于y,z优于y。
- 当 k x y > = k y z > u k_{xy}>=k_{yz}>u kxy>=kyz>u时,此时x优于y,y优于z。
无论哪种场景,y节点做为中间节点,都不能是最优值。
利用凸优化,我们就可以愉快的去掉那些中间节点,最终维护成一个斜率递增的k数组了。然后,对于每个节点i,我们只要从这个递增的k数组中,找到第一个<=i的k斜率,对应的节点,即为我们所求。
这里为什么是第一个<=i的k斜率对应的节点p呢?
对于所有小于i的斜率k[xy],都满足y优于x,x劣于y。
对于所有大于i的斜率k[xy],都满足y劣于x,x优于y。
因此,1劣于2,2劣于3,…,pos-1劣于pos,pos优于pos+1,…,n-1优于n。
好了,发现最优节点,就是pos。
最后一步,利用凸优化得到的dist,再跑一遍dijkstra,便可以得到多走1次航班路线的最短路径。
3.考虑+k次航班路线的最短路径
学会计算走1次航班路线的最短路径,走k次航班的最短路径,就重复 2过程 k次即可。
代码
代码参考了官方题解
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
#define int long long
const int inf = 1e18;
const int inf1 = 1e15;
struct CHT {
struct Point {
int x, y;
Point() {}
Point(int _x, int _y): x(_x), y(_y) {}
// 计算k 这里p节点的位置 大于当前的节点
double intersect(Point p) {
double dy = p.y - y;
double dx = x - p.x;
return dy / dx;
}
// 计算 dis_old[v] + v*v - 2*uv,
// 这里k=u x = dis_old[v] + v*v, y = -2*v
long long operator () (long long k) {
return k * x + y;
}
};
vector<double> K;
vector<Point> points;
void init(Point p) {
K.push_back(-inf);
points.push_back(p);
}
void addLine(Point p) {
// 存在 K[yz] <= k[xy]的情况 z = p, y = points[sz-1], x = points[sz-2]
// 此时丢弃中间节点y
while (points.size() >= 2 && p.intersect(points[points.size() - 1]) <= K.back()) {
K.pop_back();
points.pop_back();
}
if (!points.empty()) {
K.push_back(p.intersect(points.back()));
}
points.push_back(p);
}
long long query(long long qk) {
// 从递增的K序列中,找到最后一个 <= qk 的k[xp]对应的 p节点
int id = upper_bound(K.begin(), K.end(), qk) - K.begin();
--id;
return points[id](qk);
}
};
// dijkstra求最短路径
void dijkstra(vector<vector<pair<int, int>>> &g, vector<int> &dist) {
const int n = g.size();
vector<bool> used(n, false);
priority_queue<pair<int, int>> q;
for (int i = 0; i < n; ++i) {
q.push({ -dist[i], i });
}
while (!q.empty()) {
int v = q.top().second;
q.pop();
if (used[v]) {
continue;
}
used[v] = true;
for (auto p : g[v]) {
int u = p.first;
int w = p.second;
if (dist[u] > dist[v] + w) {
dist[u] = dist[v] + w;
q.push({ -dist[u], u });
}
}
}
}
int32_t main() {
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m, k;
cin >> n >> m >> k;
vector<vector<pair<int, int>>> g(n);
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
--u; --v;
g[u].push_back({ v, w });
g[v].push_back({ u, w });
}
vector<int> dist(n, inf1);
dist[0] = 0;
dijkstra(g, dist);// 计算不做航班的dist
for (int i = 0; i < k; ++i) {
// 计算坐了 i+1次 航班的dist
CHT cht;
cht.init(CHT::Point(0, 0));
for (int i = 1; i < n; ++i) {
cht.addLine(CHT::Point(-i * 2, dist[i] + i * i));
}
for (int i = 0; i < n; ++i) {
dist[i] = cht.query(i) + i * i;
}
// 用新的dist计算最短路径
dijkstra(g, dist);
}
for (int i : dist) {
cout << i << ' ';
}
}
最后
觉得文章不错子,可以weixin 搜索 对方正在debug,文章会首发到gongzhonghao上,一起快乐刷题吧~