본문 바로가기
ps

[백준] 1761 - 정점들의 거리

by kariskan 2022. 8. 26.

https://www.acmicpc.net/problem/1761

 

1761번: 정점들의 거리

첫째 줄에 노드의 개수 N이 입력되고 다음 N-1개의 줄에 트리 상에 연결된 두 점과 거리를 입력받는다. 그 다음 줄에 M이 주어지고, 다음 M개의 줄에 거리를 알고 싶은 노드 쌍이 한 줄에 한 쌍씩

www.acmicpc.net

 

문제를 보자마자 LCA(log)가 떠올랐다.

그런데 이 문제는 이웃한 정점 간의 거리가 주어지고, 임의의 두 정점 간의 거리를 구하는 문제이다.

 

두 가지 정도 방법이 존재하겠다.

  1. 루트 노드부터 모든 정점간의 거리를 dfs로 구하고, dis[a] + dis[b] - 2 * dis[LCA(a, b)]를 구하면 답이다.
  2. 두 정점부터 LCA를 찾아가며 거리를 더한다.

2번 먼저 코드로 보겠다.

 

#include <iostream>
#include <vector>
using namespace std;

int depth[40001], dp[40001][20], len[40001][20], n;
vector<vector<pair<int, int>>> v;

int lca(int a, int b) {

	int ret = 0;

	if (depth[a] != depth[b]) {
		if (depth[a] > depth[b]) {
			swap(a, b);
		}

		for (int i = 19; i >= 0; i--) {
			if (depth[a] <= depth[dp[b][i]]) {
				ret += len[b][i];
				b = dp[b][i];
			}
		}
	}

	if (a != b) {
		int pre = 0;
		for (int i = 19; i >= 0; i--) {
            if(dp[a][i] != dp[b][i]) {
				ret += len[a][i] + len[b][i];
				a = dp[a][i];
				b = dp[b][i];
            }
			ret += len[a][i] + len[b][i] - pre;
			pre = len[a][i] + len[b][i];
		}
	}
	return ret;
}

void go(int node, int pre, int h) {
	depth[node] = h;
	dp[node][0] = pre;

	for (auto& i : v[node]) {
		if (i.first != pre) {
			go(i.first, node, h + 1);
			len[i.first][0] = i.second;
		}
	}
}

int main() {

	cin >> n;
	v.resize(n + 1);

	for (int i = 0; i < n - 1; i++) {

		int a, b, c; cin >> a >> b >> c;

		v[a].push_back({ b,c });
		v[b].push_back({ a,c });
	}

	go(1, 0, 1);

	for (int j = 1; j < 20; j++) {
		for (int i = 1; i <= n; i++) {
			dp[i][j] = dp[dp[i][j - 1]][j - 1];
			len[i][j] = len[dp[i][j - 1]][j - 1] + len[i][j - 1];
		}
	}

	int m; cin >> m;

	for (int i = 0; i < m; i++) {
		int a, b; cin >> a >> b;
		cout << lca(a, b) << '\n';
	}

	return 0;
}

 

len 배열만 잘 보면 된다. LCA를 구할때 처럼, len[i][j] = i정점부터 2^j 번째 부모까지 거리이다.

그래서 lca를 구하는 과정에서 깊이를 맞추는 부분에서 더해주었다.

 

그다음은 아래 부분이다.

 

if (a != b) {
	int pre = 0;
	for (int i = 19; i >= 0; i--) {
		if(dp[a][i] != dp[b][i]) {
			ret += len[a][i] + len[b][i];
				a = dp[a][i];
				b = dp[b][i];
        }
		ret += len[a][i] + len[b][i] - pre;
		pre = len[a][i] + len[b][i];
    }
}

 

이 부분 같은 경우에는, 부모가 다를 경우 무조건 그 거리를 지나가야 하므로 더해준다(ret += len[a][i] + len[b][i]).

 

그런데, a와 b의 lca가 첫 번째 부모(i = 0)이고, i가 1일 때를 생각해보자. i가 1일 때도 당연히 부모가 같긴 한데, lca는 아니다. 그래서 이전 부모들끼리(2^0번째 부모와 2^1번째 부모)의 길이를 저장해놓고(pre) 거기서 빼준다.

 

 

 

다음은 1번째 방법이다. 이 부분은 기존 lca를 구하는 코드에서 정점 간의 거리만 구하면 끝이므로 조금 더 간단하다.

 

#include <iostream>
#include <vector>
using namespace std;

int depth[40001], dp[40001][20], dis[40001], n;
vector<vector<pair<int, int>>> v;

int lca(int a, int b) {

	int ret = 0;

	if (depth[a] != depth[b]) {
		if (depth[a] > depth[b]) {
			swap(a, b);
		}

		for (int i = 19; i >= 0; i--) {
			if (depth[a] <= depth[dp[b][i]]) {
				b = dp[b][i];
			}
		}
	}

	ret = a;

	if (a != b) {
		for (int i = 19; i >= 0; i--) {
			if (dp[a][i] != dp[b][i]) {
				a = dp[a][i];
				b = dp[b][i];
			}
			ret = dp[a][i];
		}
	}
	return ret;
}

void go(int node, int pre, int h, int l) {
	depth[node] = h;
	dp[node][0] = pre;
	dis[node] = l;

	for (auto& i : v[node]) {
		if (i.first != pre) {
			go(i.first, node, h + 1, l + i.second);
		}
	}
}

int main() {

	cin >> n;
	v.resize(n + 1);

	for (int i = 0; i < n - 1; i++) {

		int a, b, c; cin >> a >> b >> c;

		v[a].push_back({ b,c });
		v[b].push_back({ a,c });
	}

	go(1, 0, 1, 0);

	for (int j = 1; j < 20; j++) {
		for (int i = 1; i <= n; i++) {
			dp[i][j] = dp[dp[i][j - 1]][j - 1];
		}
	}

	int m; cin >> m;

	for (int i = 0; i < m; i++) {
		int a, b; cin >> a >> b;
		cout << dis[a] + dis[b] - 2 * dis[lca(a, b)] << '\n';
	}

	return 0;
}

'ps' 카테고리의 다른 글

[백준] 13302 - 리조트  (0) 2022.09.01
[백준] 2473 - 세 용액  (0) 2022.08.26
[백준] 1493 - 박스 채우기  (0) 2022.08.26
[백준] 1374 - 강의실  (0) 2022.08.26
[백준] 19236 - 청소년 상어  (0) 2022.08.22

댓글