cp-library

競技プログラミング用 C++ ライブラリ

View the Project on GitHub qandori/cp-library

:heavy_check_mark: Dijkstra の一意経路の探索

components/dijkstra_unique.hpp · GitHub で見る

共通ページ

Dijkstra が内部で使う、各頂点の入次数が高々 1 の場合の探索です。通常は graph/dijkstra.hpp を include し、CSR に対して dijkstra_path(g,start,target) を呼びます。条件の判定と使い分けは自動で行います。

各頂点へ入る辺が高々 1 本なら、始点から各頂点へ向かう単純経路も高々 1 本です。距離の大小による取り出し順を決める必要がないため、優先度付きキューを省けます。出辺が 1 本の区間は直接たどり、分岐した先だけを配列へ保存します。

操作 内容・返り値 計算量
shortest_path_detail::unique_path(g,start,target,inf,parent) 最短距離と始点・終点を含む頂点列を返す。到達不能は {T(-1), {}} \(O(n+m)\)

内部関数の g は size() と g[v] を持ち、g[v] は empty()・size()・添字アクセスが使える {行き先, 重み} の範囲を返すものとします。辺への参照は探索中に有効である必要があります。全頂点の入次数は高々 1、重みは非負、start != target、inf は正とします。parent は長さ \(n\) の配列を渡します。公開関数は必要な準備を行います。

始点に戻る辺を省けば、同じ頂点へ 2 回進むことはありません。始点以外で閉路へ合流するには、その頂点の入次数が 2 以上必要になるためです。目的地を最初に見つけた時点で経路が確定します。経路上の重みは始点からの順に加算し、inf 以上になる部分は探索しません。

頂点数を \(n\)、辺数を \(m\) として \(O(n+m)\) 時間・追加空間です。要素の演算・比較・コピーを \(O(1)\) とします。

依存ライブラリ

検証コード

コード

#ifndef DIJKSTRA_UNIQUE_HPP
#define DIJKSTRA_UNIQUE_HPP

#include "dijkstra_queue.hpp"
#include <algorithm>
#include <cassert>
#include <utility>
#include <vector>

namespace shortest_path_detail {
template<class G, class T>
std::pair<T, std::vector<int>> unique_path(const G &g, int st, int target, T inf, std::vector<int> &parent) {
	using State = std::pair<T, int>;
	std::vector<State> pending;
	pending.emplace_back(T(0), st);
	while (!pending.empty()) {
		auto [cost, v] = std::move(pending.back());
		pending.pop_back();
		for (const auto &first : g[v]) {
			const auto *edge = &first;
			int from = v;
			T prefix = cost;
			for (;;) {
				const auto &[to, weight] = *edge;
				assert(weight >= 0);
				if (to == st) break;
				if (reaches_limit(weight, T(inf - prefix))) break;
				T next = prefix + weight;
				if (to == target) {
					std::vector<int> path{to};
					for (int u = from; u != st; u = parent[u]) path.push_back(u);
					path.push_back(st);
					std::reverse(path.begin(), path.end());
					return {std::move(next), std::move(path)};
				}
				const auto &outgoing = g[to];
				if (outgoing.empty()) break;
				parent[to] = from;
				if (outgoing.size() != 1) {
					pending.emplace_back(std::move(next), to);
					break;
				}
				from = to;
				edge = &outgoing[0];
				prefix = std::move(next);
			}
		}
	}
	return {T(-1), {}};
}
}

#endif
Back to top page