[BOJ 23844] 백준 23844번 - 트리 정리하기
INU 코드페스티벌 2021 풀이 | ||
---|---|---|
A | [BOJ 23841] 데칼코마니 | |
B | [BOJ 23842] 성냥개비 | |
C | [BOJ 23843] 콘센트 | |
D | [BOJ 23844] 트리 정리하기 | |
E | [BOJ 23845] 마트료시카 | |
G | [BOJ 23847] INU 막대기 | |
H | [BOJ 23848] 등비수열의 합 |
1. 문제Permalink
23844. 트리 정리하기 (INU 코드페스티벌 2021 Open Contest D번)
2. 풀이Permalink
제일 깊은 depth부터, 해당 depth의 노드 개수가 a개이면, max(0,a−K)개만큼 제거한다. 이때, 자식노드의 총 개수가 적은 순서대로 제거한다.
다음 그림과 같이 트리가 구성되어 있고 K=1이라고 가정하자.
- 제일 얕은 depth부터 제거할 경우
depth=1에서 node를 한 개 제거해야 하는데 subtree1
의 총 노드 개수는 3, subtree2
의 총 노드 개수는 4이므로 subtree1
이 제거된다. 그렇게 되면 subtree2
의 depth=2에서 2개를 추가로 더 제거하여 남는 노드의 개수가 3개가 된다.
그런데 최대한 많은 노드를 남기기 위해서는 depth=1에서 subtree1
을 남겨서 답이 4가 나와야 한다. 따라서 제일 얕은 depth부터 제거하는 방식은 몇몇 경우에서 답에 도달할 수 없어 적절한 방법이 아니다.
- 제일 깊은 depth부터 제거할 경우
depth=3에서 node 1개 성립. depth=2에서 node를 3개 제거해야 하는데, 왼쪽 노드는 총 노드 개수가 2개여서 오른쪽 노드 3개가 제거된다. 따라서 depth=3에서의 최적의 결과를 계속 이어받아 최적을 유지할 수 있다. depth=1에서도 마찬가지의 이유로 오른쪽 노드가 제거되고 최종적으로 왼쪽 노드 4개만 남아 답이 4가 된다.
DFS
를 돌려서(root=1) 각 노드의 depth와 자식 노드의 총 개수(본인 포함), 부모 노드의 번호(후에 설명)을 알아낸다. 각각의 depth에서 K개만 남기고 나머지 노드들을 자식 노드의 총 개수가 적은 순서대로 제거해야 한다. 나는 priority queue
를 사용하였는데, 그냥 정렬하고 제거해도 상관없을 것 같다. (과정 중에 값이 변화하지 않으므로)
제거된 노드의 총 개수를 구하기 위해서 노드를 제거할 때, 그 노드의 자식 노드의 총 개수만큼의 값을 저장해야 한다. (코드 66줄) 이때, 제거되는 노드의 상위 노드에 개수가 그대로 남아있으면 나중에 중복 제거가 일어날 수 있으므로 그 상위노드의 개수 또한 변화시켜주어야 한다.
원래는 해당 노드의 바로 위의 상위노드 뿐만 아니라 모든 상위노드의 값을 변화시켜야 한다. 이는 너무 오버헤드가 심한 작업이므로, ch_Cnt
배열을 따로 만들어서 원래 Cnt
값과 변화한 현재 Cnt
값을 분리시킨다.
그리고 어떤 노드가 제거되면 그 노드의 원래 자식 노드의 총 개수만큼 바로 위 상위 노드의 개수에서 뺀다. (코드 67줄) 이렇게 하면 depth가 1씩 감소하므로 값을 한 단계씩 바꾸면서 문제없이 이어갈 수 있다. (이를 위해서 DFS
를 돌 때, 각 노드의 Parent node
를 저장했다.)
3. 채점 결과Permalink
4. 회고Permalink
N의 최댓값을 잘못 체크하여 RTE
를 받았다. 또한, 상위노드의 개수 변화 과정을 잘못 구현하여 WA
를 받았다.
5. 코드Permalink
#include <bits/stdc++.h> | |
using namespace std; | |
#define FASTIO cin.tie(0); cout.tie(0); ios_base::sync_with_stdio(0); | |
#define FOR(i,a,b) for(int i=(a);i<=(b);i++) | |
#define ROF(i,a,b) for(int i=(a);i>=(b);i--) | |
#define ll long long int | |
#define pii pair<int, int> | |
#define PQ priority_queue | |
#define LEN(v) int(v.size()) | |
#define ALL(v) v.begin(),v.end() | |
#define INF 2e9 | |
#define LINF 1e18 | |
int N, K; | |
vector<vector<int>> Edge(10001, vector<int>()); | |
vector<int> Depth(10001, 0); | |
vector<int> Cnt(10001, 0); | |
vector<int> ch_Cnt(10001, 0); | |
vector<int> Parent(10001, 0); | |
int max_depth = 0; | |
int DFS(int node, int prev, int depth) { | |
int cnt = 1; | |
Depth[node] = depth; | |
Parent[node] = prev; | |
max_depth = max(max_depth, depth); | |
for (int next : Edge[node]) { | |
if (next == prev) continue; | |
cnt += DFS(next, node, depth + 1); | |
} | |
Cnt[node] = cnt; | |
ch_Cnt[node] = Cnt[node]; | |
return cnt; | |
} | |
int main() { | |
FASTIO; | |
cin >> N >> K; | |
FOR(i, 1, N - 1) { | |
int a, b; | |
cin >> a >> b; | |
Edge[a].push_back(b); | |
Edge[b].push_back(a); | |
} | |
DFS(1, 0, 0); | |
vector<vector<int>> idx(max_depth + 1, vector<int>()); | |
FOR(i, 1, N) { | |
idx[Depth[i]].push_back(i); | |
} | |
int sub = 0; | |
ROF(i, max_depth, 1) { | |
int mini = INF; | |
int min_idx = 0; | |
int len = LEN(idx[i]); | |
PQ<pii> pq; | |
FOR(j, 0, len - 1) { | |
int node = idx[i][j]; | |
pq.push({ -ch_Cnt[node], node }); | |
} | |
FOR(j, 1, len - K) { | |
int cnt = -pq.top().first; | |
int node = pq.top().second; | |
pq.pop(); | |
sub += cnt; | |
ch_Cnt[Parent[node]] -= Cnt[node]; | |
} | |
} | |
cout << N - sub; | |
return 0; | |
} |
댓글남기기