Boj 1602) 도망자 원숭이
문제
어떤 경로의 시간는 그것을 이루는 간선의 가중치들과 정점의 최대 가중치를 더한 값으로 문제에서 정의된다.
우리는 경로를 모르므로 최대 가중치를 고정시키자. 시작점을 a, 끝점을 b, 최대 가중치를 가진 정점을 p 라고 하면 최단시간은 다음과 같다.
- a->p 의 최단경로 + p->b 의 최단경로 + p 의 가중치
- 이때의 최단경로는 p 의 가중치보다 작은 정점으로만 이루어져야한다.
주어진 쿼리의 특성 상 우리는 임의의 두 점 사이의 최단경로가 필요하다. 이를 위해 잘 알려진 두가지 방법으로 Floyd Warshall 과 Dijkstra 이 있고 두 방법으로 모두 풀 수 있다. 이때 2번 성질을 만족시키기 위해 약간의 트릭이 추가된다.
나는 처음에 Floyd Warshall 로 풀었고 후자의 방법은 답지를 봤는데 후자가 더 좋은방법 같다. 이 문제는 간선이 작아 속도도 더 빠르고 구현도 간단하기 때문이다.
Floyd Warshall
Floyd Warshall 의 가장 바깥 루프를 돌 때의 성질이 있다. 루프마다 시작점과 끝점 사이에 경유할 수 있는 정점이 추가된다는 것이다.
이 성질을 이용해서 중간경로가 되는 정점을 작은 가중치를 가진 정점 순서로 추가한다. 그러면 2번 성질을 만족하는 최단경로를 구할 수 있다.
시간 복잡도
O(\(N^3 + NQ\))
코드
const int MAX_IN = 501;
int edges[MAX_IN][MAX_IN];
int nodes[MAX_IN];
int ranks[MAX_IN];
int main()
{
fill(edges[0], edges[MAX_IN], 1e9);
for (int i = 1; i <= MAX_IN; i++) edges[i][i] = 0;
int n, m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) cin >> nodes[i];
for (int i = 0; i < n; i++) ranks[i] = i+1;
sort(ranks, ranks + n, [](int a, int b) { return nodes[a] < nodes[b]; });
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[a][b] = edges[b][a] = c;
}
vector<pair<int,int>> queries;
for(int i = 0; i < q; i++)
{
int s, e; cin >> s >> e;
queries.emplace_back(s, e);
}
vector<int> ans(q, 1e9);
for (int k = 0; k < n; k++)
{
int mid = ranks[k];
for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
if (edges[i][j] > edges[i][mid] + edges[mid][j])
edges[i][j] = edges[i][mid] + edges[mid][j];
for (int i = 0; i < q; i++)
{
int s = queries[i].first, e = queries[i].second;
if (edges[s][mid] != 1e9 && edges[mid][e] != 1e9)
{
int w = max(nodes[mid], max(nodes[s], nodes[e]));
ans[i] = min(ans[i], edges[s][mid] + edges[mid][e] + w);
}
}
}
for(auto an : ans) cout << (an == 1e9 ? -1 : an) << '\n';
}
Dijkstra
최대가중치를 가질 임의의 정점으로부터의 최단경로를 구하는 것은 Dijkstra 를 \(N\) 번 돌려서 해결할 수 있다.
이때 시작점보다 가중치가 같거나 작은 정점만 지나야 하는데 조건문에 조건을 하나 더 추가하면 간단히 처리할 수 있다.
시간 복잡도
O(\(NE\log{E} + NQ\))
- 주어진 조건에서 \(E\) 의 크기가 작아서 플로이드보다 더 빠르다.
코드
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_IN = 501;
vector<E> edges[MAX_IN];
int dp[MAX_IN][MAX_IN];
int nodes[MAX_IN];
int n; // vertex number
void Dijkstra(int start, int* dp)
{
fill(dp, dp + n + 1, 1e9);
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;
for (E& l : edges[cur.d])
if (dp[cur.d] + l.w < dp[l.d] &&
nodes[start] >= nodes[l.d]) // equal 조건 필요함에 주의
{
dp[l.d] = dp[cur.d] + l.w;
q.push({ l.d, dp[l.d] });
}
}
}
int main()
{
int m, q;
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) cin >> nodes[i];
for (int i = 0; i < m; i++)
{
int a, b, c;
cin >> a >> b >> c;
edges[a].push_back({ b, c });
edges[b].push_back({ a, c });
}
for (int i = 1; i <= n; i++) Dijkstra(i, dp[i]);
for(int i = 0; i < q; i++)
{
int s, e, ans = 1e9; cin >> s >> e;
for (int mid = 1; mid <= n; mid++)
ans = min(ans, dp[mid][s] + dp[mid][e] + nodes[mid]);
cout << (ans == 1e9 ? -1 : ans) << '\n';
}
}
댓글남기기