はじめてのk-th shortest path - 3255 Roadblocks

3255 Roadblocks


1からNまでのパスの内、2番目に短いものを探す。


後で調べてわかったけどこれはk-th shortest pathという問題らしい。

そういえばアルゴリズムの講義でやった気もしないでもないけど、

とりあえずchokudaiくんの教えにしたがって、

アルゴリズムを知らなくても自分なりに考え取り組んでみた。


ダイクストラでは最短路を求めることしかできない。
でもダイクストラの考え方は使えそうだ。
更に考えると、ダイクストラはvisited[]やlabel[next] > curr.cost + edge.wなどの縛りが強いせいで(おかげで)最短路しか求められないのである。


これらの縛りをなくしてあげれば、while (!Q.empty())ループを回している限り、
パスを生成し続ける。(いつかはMLEだけど)


でもそれだけだと、2番目のパスを求めることはできない。
各パスはランダムな順序でゴールに至るため、いつまでループを回したらいいのかわからないのである。
そこで、何らかの形でpriority_queueに順序付けをしてやらなければならない。


そこで考えたのがゴールからの距離である。

通常のダイクストラの[それまでの距離]というソート基準に加えて
[ゴールまでの距離]を加えてやれば、priority_queueが、ゴールに近い状態から吐きだしてくれるため、最短路、2番目の最短路、3番目の最短路、、、という風に求められそうである。

ゴールまでの距離は普通にダイクストラで計算すればいい。


こんなことに気づいてしまうなんて、ちょっとできる子なんじゃないの(ニヤリ
としていたけど、きっと昔習ったのが頭の片隅にあっただけだろうな。。。

#include<cstdio>
#include<vector>
#include<queue>
#include<climits>
#include<cstring>

using namespace std;

struct State {
    int n, w;
    State(int n, int w) :
	n(n), w(w) {}
    bool operator < (const State &s) const {
	return w > s.w;
    }
};

struct Edge {
    int n, w;
    Edge (int n, int w) :
	n(n), w(w) {}
};

typedef vector<Edge> Edges;
typedef vector<Edges> Graph;

/*
 * ダイクストラみたいなアルゴリズムで
 * k番目に短いパスを求める
 * この問題では1-2-1-3のようなパスも許される
 * このようなパスを禁止するには、visited[]を用意して
 * 条件付きでcontinueを使い、
 * goalに着いたとき、visited[goal] = falseにしてやればよい
 * (ゴールに至る他のパスを探すため)
 */
int k_shortestPath(const Graph &g, int s, int e, int k) { // gはグラフ、sとeはスタートゴール
    int N = g.size();
    int dist[N];  // dist[i]はゴールからiまでの距離
    bool visited[N];
    for (int i = 0; i <= N; ++i) dist[i] = INT_MAX;
    dist[e] = 0;  // ゴールからゴールへの距離は0(これを入れ忘れてMLE連発した。。。)
    memset(visited, false, sizeof(visited));
    priority_queue<State> Q;
    // ゴールから各ノードへの距離を求める(普通のdijkstra)
    Q.push(State(e, 0));
    while (!Q.empty()) {
	State cr = Q.top(); Q.pop();
	if (visited[cr.n]) continue;
	visited[cr.n] = true;
	for (int i = 0; i < g[cr.n].size(); ++i) {
	    Edge edg = g[cr.n][i];
	    if (visited[edg.n]) continue;
	    if (dist[edg.n] > cr.w + edg.w) {
		dist[edg.n] = cr.w + edg.w;
		Q.push(State(edg.n, cr.w + edg.w));
	    }
	}
    }
    int cnt = 0; // ゴールカウント
    priority_queue<State> R;
    /* priority_queue Rは
     * [それまでの距離+そのノードからゴールまでの距離]
     * でソートされる。
     * これによりゴールに近い状態から取り出される
     */
    R.push(State(1, 0 + dist[1])); 
    while (!R.empty()) {
	State cr = R.top(); R.pop();
	cr.w -= dist[cr.n]; // cr.wをそれまでの距離に戻す
	if (cr.n == e && ++cnt == k) return cr.w; // k回目にゴールしたらそれまでの距離を返す
	for (int i = 0; i < g[cr.n].size(); ++i) {
	    Edge edg = g[cr.n][i];
	    R.push(State(edg.n, cr.w + edg.w + dist[edg.n]));
	}
    }
    return -1;
}

int main() {
    int N, R;
    scanf("%d %d", &N, &R);
    Graph g(N + 1);
    for (int i = 0; i < R; ++i) {
	int A, B, D;
	scanf("%d %d %d", &A, &B, &D);
	g[A].push_back(Edge(B, D));
	g[B].push_back(Edge(A, D));
    }
    printf("%d\n", k_shortestPath(g, 1, N, 2));
}