https://www.acmicpc.net/problem/1647
1647번: 도시 분할 계획
첫째 줄에 집의 개수 N, 길의 개수 M이 주어진다. N은 2이상 100,000이하인 정수이고, M은 1이상 1,000,000이하인 정수이다. 그 다음 줄부터 M줄에 걸쳐 길의 정보가 A B C 세 개의 정수로 주어지는데 A번
www.acmicpc.net
입력받은 그래프를 두 개의 트리로 만드는 문제이다. 이 때 만들어진 두 트리의 가중치의 합을 최소화해야 하므로, 이 문제는 최소 스패닝 트리 문제라고 할 수 있다.
- 크루스칼 기법
크루스칼 기법을 사용하면서 트리의 개수를 추적하다가, 트리의 개수가 2개 이하로 내려가는 순간 기법을 멈추고 가중치의 총합을 출력한다.
후술할 프림 알고리즘과 마찬가지로 트리를 완성한 뒤 트리의 가중치에 최대 에지 가중치를 뺀 값을 출력해도 되지만, 이 방법은 트리의 개수를 추적하는 방법과 완벽히 똑같이 작동하면서, 거기에 추가로 더 작업을 진행해야 하므로 효율이 떨어진다.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
typedef pair<int, pair<int, int>> edge;
#define MAX_N 100000
#define _C first
#define _A second.first
#define _B second.second
// N: 주어진 조건에서 노드 수
// parent[i]:각 노드가 속한 트리에서 노드의 부모, 혹은 루트 노드
int N, M;
int parent[MAX_N + 1] = { 0, };
// 에지를 저장할 공간
vector<edge> v;
// 입력받은 노드를 포함하는 트리의 루트를 재귀적으로 탐색하여 반환
int root(int node) {
if (parent[node] == node || !parent[node]) return node;
return root(parent[node]);
}
int main() {
// 입출력 속도 향상
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
// M: 에지의 수
// cnt: 현재 에지들이 이루는 트리의 수, ans: 모든 트리의 가중치의 합
int M, cnt, ans = 0, A, B, C;
// 노드와 에지의 수를 입력받은 뒤
cin >> N >> M;
// 트리의 수를 노드의 수로 설정
cnt = N;
// 에지의 집합 v의 크기를 정의
v = vector<edge>(M);
// 각 에지를 입력받은 뒤 cost 순으로 정렬
for (edge &e : v) cin >> e._A >> e._B >> e._C;
sort(v.begin(), v.end());
// cost가 작은 에지부터 순서대로
for (edge e : v) {
// 트리의 수를 2개 미만으로 줄였다면 루프를 탈출
if (cnt <= 2) break;
// 현재 에지의 두 노드를 각 노드가 속한 트리의 루트로 치환
// 두 노드가 같은 트리에 속해 있다면 continue
A = root(e._A);
B = root(e._B);
if (A == B) continue;
// 서로 다른 트리를 에지로 연결했으므로 cnt를 1 줄인 뒤
cnt--;
// ans에 현재 에지의 가중치를 더하고
ans += e._C;
// 두 루트 노드 중 값이 더 작은 노드를 루트 노드로 설정
parent[(A > B ? A : B)] = (A < B ? A : B);
}
// 가중치의 합을 출력
cout << ans << '\n';
return 0;
}
-프림 기법
최소 스패닝 트리를 만들면서 사용된 에지의 가중치의 최댓값을 따로 저장하고, 트리의 가중치에 최대 에지 가중치를 뺀 값을 출력하면 된다.
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int, int> edge;
#define MAX_N 10
#define _cost first
#define _node second
bool visit[MAX_N + 1];
int main() {
// 입출력 속도 향상
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
// N, M: 그래프의 노드와 에지의 개수
// max: 최소 스패닝 트리에 쓰인 가중치의 최댓값
// sum: 최소 스패닝 트리의 가중치의 총합
// A, B, C: 노드 A, B를 잇고 가중치가 C인 에지
int N, M, max = 0, sum = 0, A, B, C;
priority_queue<edge, vector<edge>, greater<edge>> q;
vector<edge> v[MAX_N + 1];
// 그래프를 입력받은 뒤
cin >> N >> M;
while (M--) {
cin >> A >> B >> C;
v[A].push_back({C, B});
v[B].push_back({C, A});
}
// 1번 노드를 최소 스패닝 트리의 루트 노드로 설정
// 1번 노드를 방문한 뒤 1번 노드와 연결된 에지를 모드 q에 push
visit[1] = true;
for (edge e : v[1]) q.push(e);
// 모든 접근 가능한 에지에 대해
while (!q.empty()) {
// q에서 에지를 하나 가져온 뒤
edge e = q.top();
q.pop();
// 에지와 연결된 노드에 방문한 적 있다면 continue
if (visit[e._node]) continue;
// 트리의 가중치에 에지의 가중치를 더한 뒤
sum += e._cost;
// 가중치의 최댓값을 갱신한 후
if (max < e._cost) max = e._cost;
// 에지에 연결된 노드에 방문
visit[e._node] = true;
// 방문한 노드에 연결된 모든 접근 가능한 에지를 q에 push
for (auto e_ : v[e._node]) if (!visit[e_._node]) q.push(e_);
}
// 트리의 가중치의 합에서 트리에서 가장 큰 에지의 가중치를 뺀 값을 출력
cout << sum - max << '\n';
return 0;
}
'알고리즘 > 문제 풀이' 카테고리의 다른 글
1676번: 팩토리얼 0의 개수 (0) | 2021.12.09 |
---|---|
1654번: 랜선 자르기 (0) | 2021.12.09 |
1644번: 소수의 연속합 (0) | 2021.12.09 |
1629번: 곱셈 (0) | 2021.12.09 |
1620번: 나는야 포켓몬 마스터 이다솜 (0) | 2021.12.09 |