Boj 5250) 최단 경로들
문제
설명
시작점과 끝점이 주어지고 어떤 최단경로 \(S\) 가 주어진다. 그리고 최단경로의 간선 각각이 사라졌을 때의 최단경로를 구해야한다.
각 간선이 사라졌을 때마다 다익스트라나 SPFA 를 사용해서 최단경로를 구해도 주어진 조건 상 아슬아슬하게 가능하다고 한다. 하지만 정해는 다음과 같을 것이다.
-
\(S\) 중 간선 하나가 사라졌다면 최단경로는 \(S\) 이외의 간선을 당연히 경유한다.
-
특정한 간선 \(K\) 을 지나는 최단경로는 시작점과 끝점 각각에서 최단경로로 이루어진 트리 를 구해서 이 둘을 \(K\) 를 이용해 이으면 된다.
- 이 경로는 \(S\) 와 일부 겹치게 되어, 시작점/끝점 각각마다 마지막으로 \(S\) 와 겹치는 지점이 존재하게 된다.
- 최단경로의 특성 상 시작점/끝점 부터 \(K\) 겹치다가 갈라지는 경우만 존재하기 때문이다. 다시말해 다시 \(S\) 로 합류하지 않는다.
- 이는 곧 사라지는 간선에 영향을 받지 않는 범위와 같다. 이를 바탕으로 효율적으로 쿼리하면 된다.
나도 위와 비슷한 발상을 했지만 한군데에서 논리적 비약을 해서 삽질을 해버렸다. 지금부터 내가 한 삽질을 기술하도록 하겠다.
삽질
위에 대해서 나는 간선이 아니라 정점을 중심으로 생각했다. 즉 1번을 \(S\) 이외의 정점을 지난다고 생각했다. 그리고 \(S\) 사이의 간선은 중간에 임시정점을 삽입해서 해결하였다.
여기까지는 괜찮다. 하지만 문제는 2번이다.
임의의 정점을 반드시 지나는 최단경로는 임의의 정점과 시작점/끝점 사이의 최단거리의 합인가?
위 명제는 적어도 나에겐 그럴듯했으나 틀렸다.
예를들어 다음과 같은 반례가 있다.
5 5 5 3
1 2 1
2 3 1
1 4 1
2 4 5
5 1 1
4
5 1 2 3
왜냐하면 최단경로 트리 상의 간선 이외의 간선으로 더 빨리 갈 수 있기 때문이다.
시간 복잡도
O(\(\mathrm{N} \log{\mathrm{M}} + \mathrm{M} \log{\mathrm{N}}\))
- 다익스트라 구현 + 세그구성
코드
template<typename T, size_t _H>
class SegmentTree
{
template<typename F>
struct Node {
Node() {}
Node(F v) : v(v) {}
F operator+(const Node& in) { return min(v, in.v); }
bool Lazy() { return lazy != 1e9; }
void LazyPropa(F in) { lazy = min(lazy, in); }
F v = 1e9, lazy = 1e9;
};
public:
void Update(int l, int r, T v) { assert(r <= INDEX_MAX); _t1 = l; _t2 = r; _t3 = v; Update_Recursive(1, 1, INDEX_MAX); }
Node<T> Query(int l, int r) { assert(r <= INDEX_MAX); _t1 = l; _t2 = r; return Query_Recursive(1, 1, INDEX_MAX); }
private:
void LazyPropa(int x, int s, int e, T v)
{
nodes[x].v = min(nodes[x].v, v);
if (s != e) {
nodes[2 * x].LazyPropa(v);
nodes[2 * x + 1].LazyPropa(v);
}
}
void Update_Recursive(int x, int s, int e)
{
if (nodes[x].Lazy()) // 기존에 Lazy 가 있는경우 자식으로 Propa 함
LazyPropa(x, s, e, nodes[x].lazy);
if (_t1 <= s && e <= _t2) // 현재 범위가 업데이트할 범위 내에 있는 경우
LazyPropa(x, s, e, _t3);
else if (!(_t2 < s || e < _t1)) { // 현재 범위가 업데이트할 범위에 걸치는 경우
int m = (s + e) / 2;
Update_Recursive(2 * x, s, m);
Update_Recursive(2 * x + 1, m + 1, e);
nodes[x] = nodes[2 * x] + nodes[2 * x + 1];
}
}
Node<T> Query_Recursive(int x, int s, int e)
{
if (nodes[x].Lazy())
LazyPropa(x, s, e, nodes[x].lazy);
if (_t2 < s || e < _t1) return {};
if (_t1 <= s && e <= _t2) return nodes[x];
int m = (s + e) / 2;
return Query_Recursive(x * 2, s, m) + Query_Recursive(x * 2 + 1, m + 1, e);
}
public:
Node<T> nodes[1 << _H];
int INDEX_MAX = 1 << _H - 1;
int _t1, _t2;
T _t3;
};
SegmentTree<int, 12> seg;
struct E { int d, w; };
namespace std {
template<> struct greater<E> {
bool operator()(const E& a, const E& b) const { return a.w > b.w; }
};
}
const int MAX_W = 1e9;
const int MAX_IN = 2001;
int n; // vertex number
vector<E> edges[MAX_IN];
int shortPathIdx[MAX_IN];
int s_dp[MAX_IN], e_dp[MAX_IN];
int s_ptb[MAX_IN], e_ptb[MAX_IN];
void Dijkstra(int start, int* dp)
{
fill(dp, dp + MAX_IN, MAX_W);
priority_queue<E, vector<E>, greater<E>> q;
dp[start] = 0; q.push({ start, 0 });
while (!q.empty())
{
E cur = q.top(); q.pop();
if (dp[cur.d] < cur.w) continue; // out of updated edge
for (E& e : edges[cur.d])
if (dp[cur.d] + e.w < dp[e.d])
{
dp[e.d] = dp[cur.d] + e.w;
q.push({ e.d, dp[e.d] });
}
}
}
void DAG(int cur, int spar, int* dp, int* ptb)
{
if (ptb[cur]) return;
ptb[cur] = spar = shortPathIdx[cur] ? cur : spar;
for (E& e : edges[cur])
{
if (dp[cur] + e.w != dp[e.d]) continue;
// 주어진 최단경로가 포함되도록 트리 구성
if (shortPathIdx[e.d] && !shortPathIdx[cur]) continue;
DAG(e.d, spar, dp, ptb);
}
}
int main()
{
int m, a, b;
cin >> n >> m >> a >> b;
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
edges[u].push_back({ v, w });
edges[v].push_back({ u, w });
}
int k; vector<int> path;
cin >> k;
for (int i = 0; i < k; i++)
{
int tmp;
cin >> tmp;
path.emplace_back(tmp);
shortPathIdx[tmp] = i + 1;
}
Dijkstra(a, s_dp);
Dijkstra(b, e_dp);
DAG(path.front(), path.front(), s_dp, s_ptb);
DAG(path.back(), path.back(), e_dp, e_ptb);
for (int i = 1; i <= n; i++)
for (E& e : edges[i])
{
// 주어진 최단거리 상의 간선이면 제외
if (shortPathIdx[i] && shortPathIdx[e.d] && shortPathIdx[e.d] - shortPathIdx[i] == 1) continue;
int p1 = shortPathIdx[s_ptb[i]], p2 = shortPathIdx[e_ptb[e.d]];
int w = e.w + s_dp[i] + e_dp[e.d];
seg.Update(p1, p2-1, w);
}
for (int i = 1; i < k; i++)
{
int ans = seg.Query(i, i).v;
cout << (ans >= MAX_W ? -1 : ans) << '\n';
}
}
댓글남기기