tree dynamic programming 유형이다. BOJ 캠프 문제인데, 아마 tree dp의 기본이 되는 문제일 것이다. 그 당시 백준님의 풀이를 보고 풀었는데, 지금 보니 기억이 안나서 복습하려고 한다. 다시 풀어보고 혼자 생각해 봐야겠다.
한 노드에서 시작해서, 선택할 경우, 이쪽 자식으로는 몇 개, 저 쪽 자식으로는 몇 개를 선택할 건지에 대해 가능한 경우를 다 해봐야 할 것 같은데 그럼 선택할 수 있는 경우가 너무 많아진다.... 아 그렇기 때문에 백준님의 풀이에서도 이렇게 하지 않는다.
백준님 풀이를 보고 다시 공부해야겠다.
내가 위에서 시도했던 방법의 경우, 한 노드를 선택한 후 그 아래 자식 노드들을 선택할 수 있는 경우가 너무 많기 때문에 문제를 두 개의 부분으로 나눠서 접근해보자.
일단 입력으로 주어지는 트리는 그래프 형태로 주어지기 때문에, 어느 한 노드를 root로 잡고 트리로 재구성을 해준다.(혹은 parent배열을 만들어서 사용해도 트리로 구성한 효과가 있지만, 트리로 재구성을 하면 자식의 개수도 좀 더 편하게 파악할 수 있고...이 문제에서는 트리로 재구성하는 것이 훨씬 편하다.) 그러면 문제에서 구하는 경우는 어떤 경우이든 간에 트리의 일부가 되기 때문에 어떤 노드를 root로 하는 sub tree에서 그 root를 포함시켜 k개의 노드를 고르는 방법의 수를 각 노드에 대해 구하면 모든 경우를 커버할 수 있고 겹치는 경우도 없을 것이다. 결국 각 노드를 root로 하는 sub tree에서의 경우의 수들을 구해서 다 합하면 답이 된다.
그렇다면 어떤 노드를 root로 하는 sub tree에서의 경우의 수를 어떻게 구할 것인가? 이제 두 개의 부분으로 나눠서 접근해보자. 바로 node의 자식 중 맨 왼쪽 자식을(편의상... 그냥 어느 한 자식이기만 하면 된다) root로 하는 sub tree와 그 나머지로 나눌 수 있다.
그 나머지인 subtree는 node가 root인데, 맨 왼쪽 자식 하나를 root로 하는 sub tree가 분리되어 나갔으므로 원래 tree와는 좀 다르다. 그래서 d[node][e][k]로 표현을 해주는데,
d[node][e][k] = node가 루트일 때, node를 포함해서 k개의 노드를 선택하는 방법의 수 (왼쪽부터 e개의 자식을 root로 하는 sub tree들을 제외한 sub tree에서)
이 된다.
d[node][e][k] = SUM( i = 0~k-1 , d[leftmost child][0][i]*d[node][e+1][k-i] ) 가 될 것이다. 여기서 주의해야 할 것이, node는 무조건 선택해야 하므로 k-i가 0이 되지 않도록 i는 최대 k-1까지이다. 그리고 i가 0일 때는 child를 선택하지 않는 다는 것인데 경우의 수가 곱셈으로 이루어지므로 1을 return해주면 된다.
//코드
//baekjoon님 풀이 슬라이드+ baekjoon님 코드 보고 작성한 코드
#include <cstdio>
#include <cstring>
#include <vector>
using
namespace
std;
typedef
long
long
ll;
void
makeTree(
int
,
int
);
ll treeCity(
int
,
int
,
int
);
const
ll MOD=1e9+7;
vector<
int
> adj[51];
vector<
int
> tree[51];
ll d[51][50][51];
int
main() {
int
n, k;
scanf
(
"%d %d"
, &n, &k);
for
(
int
i=0; i<n-1; i++) {
int
u, v;
scanf
(
"%d %d"
, &u, &v);
adj[u].push_back(v);
adj[v].push_back(u);
}
makeTree(1, -1);
memset
(d, -1,
sizeof
(d));
ll ans=0;
for
(
int
i=1; i<=n; i++) {
ans+=treeCity(i, 0, k);
ans%=MOD;
}
printf
(
"%lld\n"
, ans);
return
0;
}
ll treeCity(
int
root,
int
e,
int
k) {
if
(k==0)
return
1;
//root를 선택하지 않는 경우 return 1
if
(e==tree[root].size()) { //선택할 것이 root만 남은 경우!(자식이 없는 경우도 마찬가지)
return
k==1;
}
ll& ret=d[root][e][k];
if
(ret!=-1)
return
ret;
ret=0;
int
child=tree[root][e];
for
(
int
i=0; i<k; i++) {
//맨 처음의 root는 무조건 선택해야 하므로 k-i가 0이되면 안된다!***
//맨 처음 root외에 root들은(즉 맨 처음 root의 자식들) 선택되지 않아도 된다.
//그리고 그 경우(root의 child가 선택되지 않는 경우)는 위쪽에서 k==0일 때, return 1;
ret+=(treeCity(child, 0, i)*treeCity(root, e+1, k-i));
ret%=MOD;
}
return
ret;
}
void
makeTree(
int
root,
int
parent) {
for
(
int
i=0; i<adj[root].size(); i++) {
if
(adj[root][i]!=parent) {
tree[root].push_back(adj[root][i]);
makeTree(adj[root][i], root);
}
}
}
그리고 코드를 다시 짜보면서 실수한 것이 가장 왼쪽의 child를 선택할 때, d[node][0]을 가장 왼쪽 노드로 봤는데, 이러면 처음과 왼쪽 sub tree에서만 맞고, 오른쪽 sub tree에서는 맞지 않다. 그렇다면 어떻게? 바로 e를 이용하는 것이다. 자식 중에서 왼쪽 e개를 제외한 것이므로 맨 왼쪽 자식은 e번째 (0번째 자식부터 시작할 때) 자식이 된다!
그리고 node가 50개 밖에 안되서 k개 뽑는 경우의 수가 int형으로 충분히 커버가능할 줄 알았는데 생각해보니 k값도 최대 50까지 가능하므로 k값에 따라 k개를 뽑는 경우의 수가 매우 커질 수 있다. 그렇기 때문에 long long을 사용해주고, MOD로 꼭 modulo연산을 해줘야 한다.
출처 : 백준님 강의, 강의 자료
댓글 없음:
댓글 쓰기