Skip to content

Commit d0767d3

Browse files
committed
Add Monge shortest path
1 parent 6e200e5 commit d0767d3

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#pragma once
2+
#include <cassert>
3+
#include <vector>
4+
5+
// Shortest path of Monge-weighted graph
6+
// Variant of LARSCH Algorithm: https://noshi91.hatenablog.com/entry/2023/02/18/005856
7+
// Complexity: O(n log n)
8+
//
9+
// Given a directed graph with n vertices and weighted edges
10+
// (w(i, j) = cost_callback(i, j) (i < j)),
11+
// this class calculates the shortest path from vertex 0 to all other vertices.
12+
template <class Cost> struct monge_shortest_path {
13+
std::vector<Cost> dist; // dist[i] = shortest distance from 0 to i
14+
std::vector<int> amin; // amin[i] = previous vertex of i in the shortest path
15+
16+
template <class F> void _check(int i, int k, F cost_callback) {
17+
if (i <= k) return;
18+
if (Cost c = dist[k] + cost_callback(k, i); c < dist[i]) dist[i] = c, amin[i] = k;
19+
}
20+
21+
template <class F> void _rec_solve(int l, int r, F cost_callback) {
22+
if (r - l == 1) return;
23+
24+
const int m = (l + r) / 2;
25+
for (int k = amin[l]; k <= amin[r]; ++k) _check(m, k, cost_callback);
26+
27+
_rec_solve(l, m, cost_callback);
28+
for (int k = l + 1; k <= m; ++k) _check(r, k, cost_callback);
29+
_rec_solve(m, r, cost_callback);
30+
}
31+
32+
template <class F> Cost solve(int n, F cost_callback) {
33+
assert(n > 0);
34+
dist.resize(n);
35+
amin.assign(n, 0);
36+
37+
dist[0] = Cost();
38+
for (int i = 1; i < n; ++i) dist[i] = cost_callback(0, i);
39+
40+
_rec_solve(0, n - 1, cost_callback);
41+
42+
return dist.back();
43+
}
44+
45+
template <class F> int num_edges() const {
46+
int ret = 0;
47+
for (int c = (int)amin.size() - 1; c >= 0; c = amin[c]) ++ret;
48+
return ret;
49+
}
50+
};
51+
52+
// Find shortest path length from 0 to n - 1 with k edges, min_edges <= k <= max_edges
53+
// https://noshi91.hatenablog.com/entry/2022/01/13/001217
54+
template <class Cost, class F>
55+
Cost monge_shortest_path_with_specified_edges(int n, int min_edges, int max_edges,
56+
Cost max_abs_cost, F cost_callback) {
57+
58+
assert(1 <= n);
59+
assert(0 <= min_edges);
60+
assert(min_edges <= max_edges);
61+
assert(max_edges <= n - 1);
62+
63+
monge_shortest_path<Cost> msp;
64+
65+
auto eval = [&](Cost p) -> Cost {
66+
msp.solve(n, [&](int i, int j) { return cost_callback(i, j) - p; });
67+
return -msp.dist.back() - p * (p < 0 ? max_edges : min_edges);
68+
};
69+
70+
Cost lo = -max_abs_cost * 3, hi = max_abs_cost * 3;
71+
72+
while (lo + 1 < hi) {
73+
Cost p = (lo + hi) / 2, f = eval(p), df = eval(p + 1) - f;
74+
if (df == Cost()) {
75+
return -f;
76+
} else {
77+
(df < Cost() ? lo : hi) = p;
78+
}
79+
}
80+
81+
Cost flo = eval(lo), fhi = eval(hi);
82+
83+
return flo < fhi ? -flo : -fhi;
84+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
title: Shortest path of DAG with Monge weights
3+
documentation_of: ./monge_shortest_path.hpp
4+
---
5+
6+
$n$ 頂点の DAG で辺重みが Monge となるようなものに対して最短路長を高速に求める. [1] で紹介されている簡易版 LARSCH Algorithm が実装されていて,計算量は $O(n \log n)$ .
7+
8+
また,辺数が `min_edges` 以上 `max_edges` 以下であるようなものに限った最短路長を高速に求めることも可能(計算量にさらに重み二分探索の $\log$ がつく).
9+
10+
## 使用方法
11+
12+
### 最短路長の計算
13+
14+
```cpp
15+
auto f = [&](int s, int t) -> Cost {
16+
//
17+
};
18+
19+
monge_shortest_path<Cost> msp;
20+
Cost ret = msp.solve(n, f);
21+
```
22+
23+
### 辺の本数の下限・上限を指定した最短路長の計算
24+
25+
```cpp
26+
auto f = [&](int s, int t) -> Cost {
27+
//
28+
};
29+
30+
int n; // 頂点数
31+
int l, r; // 辺の本数が [l, r] の範囲に収まる最短路を見つけたい
32+
Cost max_weight; // f() が返す値の絶対値の上界
33+
34+
Cost ret = monge_shortest_path_with_specified_edges(n, l, r, max_weight, f);
35+
```
36+
37+
## 問題例
38+
39+
- [No.705 ゴミ拾い Hard - yukicoder](https://yukicoder.me/problems/no/705)
40+
- [AtCoder Beginner Contest 218 H - Red and Blue Lamps](https://atcoder.jp/contests/abc218/tasks/abc218_h)
41+
- [東京海上日動プログラミングコンテスト2024(AtCoder Beginner Contest 355) G - Baseball](https://atcoder.jp/contests/abc355/tasks/abc355_g)
42+
- [東北大学プログラミングコンテスト 2022 K - Lebesgue Integral](https://atcoder.jp/contests/tupc2022/tasks/tupc2022_k)
43+
44+
## Links
45+
46+
- [1] [簡易版 LARSCH Algorithm - noshi91のメモ](https://noshi91.hatenablog.com/entry/2023/02/18/005856)
47+
- [2] [Aliens DP における二分探索の色々 - noshi91のメモ](https://noshi91.hatenablog.com/entry/2023/11/20/052227#fn-c9578a2a)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/705"
2+
#include "../monge_shortest_path.hpp"
3+
4+
#include <cassert>
5+
#include <cmath>
6+
#include <iostream>
7+
using namespace std;
8+
9+
int main() {
10+
cin.tie(nullptr), ios::sync_with_stdio(false);
11+
12+
int N;
13+
cin >> N;
14+
vector<int> A(N), X(N), Y(N);
15+
for (auto &a : A) cin >> a;
16+
for (auto &x : X) cin >> x;
17+
for (auto &y : Y) cin >> y;
18+
19+
auto weight = [&](int j, int i) {
20+
assert(j < i);
21+
--i;
22+
const long long dx = abs(A.at(i) - X.at(j)), dy = Y.at(j);
23+
return dx * dx * dx + dy * dy * dy;
24+
};
25+
26+
monge_shortest_path<long long> msp;
27+
cout << msp.solve(N + 1, weight) << '\n';
28+
}

0 commit comments

Comments
 (0)