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번)

백준 23844번 - 트리 정리하기 (https://www.acmicpc.net/problem/23844)

2. 풀이Permalink

제일 깊은 depth부터, 해당 depth의 노드 개수가 a개이면, max(0,aK)개만큼 제거한다. 이때, 자식노드의 총 개수가 적은 순서대로 제거한다.

boj-23844

다음 그림과 같이 트리가 구성되어 있고 K=1이라고 가정하자.

  • 제일 얕은 depth부터 제거할 경우

depth=1에서 node를 한 개 제거해야 하는데 subtree1의 총 노드 개수는 3, subtree2의 총 노드 개수는 4이므로 subtree1이 제거된다. 그렇게 되면 subtree2depth=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줄) 이렇게 하면 depth1씩 감소하므로 값을 한 단계씩 바꾸면서 문제없이 이어갈 수 있다. (이를 위해서 DFS를 돌 때, 각 노드의 Parent node를 저장했다.)

3. 채점 결과Permalink

boj-23844

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;
}

댓글남기기