문제
- 문제 링크
BOJ 32583 - Watchdogs
$N$개의 도시로 이루어진 마을 코코르코프 속에서 $K$마리의 쥐가 이동한다. 구체적으로 $K$개의 도시쌍 $(A, B)$가 주어질 때 다음을 만족하는 도시 $C$ 중 적어도 한 곳에 고양이를 배치하려고 한다.
$|d(C, A)−d(C, B)| ≤ 1$ 도시 $C$는 $A$와 $B$ 사이의 경로에 포함된다.
한 고양이는 여러 마리의 쥐를 처리할 수 있으며 처음 배치된 자리에 머무를 때, $K$마리의 쥐를 감시하기 위해 최소 몇 마리의 고양이를 배치해야 할지 구해보자.
TL : $5$ sec, ML : $1024$ MB
$2 ≤ N ≤ 10^5$ $0 ≤ K ≤ 10^5$
알고리즘 분류
- 트리 (trees)
- 최소 공통 조상 (lowest common ancestor)
- 다이나믹 프로그래밍 (dynamic programming)
- 트리에서의 다이나믹 프로그래밍 (dynamic programming on trees)
풀이
트리에서 임의의 두 정점 $(u, v)$에 대해 중간에 위치하는 정점은 $LCA$를 이용해 $O(logN)$에 쉽게 구해줄 수 있다.
$LCA(u, v)$를 구한 뒤 깊이가 더 낮은 정점에서 $\frac{d(u, v)}{2}$만큼 리프팅을 해주면 되는데, 보다 일반화된 문제를 풀어보고자 한다면 BOJ 13511 - 트리와 쿼리 2 를 먼저 풀고 오도록 하자.
한편, 문제의 정의에 따르면 $d(u, v)$가 짝수일경우 중간 도시가 하나로 특정되어 바로 고양이를 배치하면 쉽게 해결할 수 있다. 문제는 $d(u, v)$가 홀수일 때인데, 이 경우 문제의 조건을 만족하는 중간 도시가 둘이 되어 버린다.
두 중간 도시가 간선 하나로 이어져있는 상황에서 둘 중 한 곳에만 고양이를 배치하면 된다는 점에 주목해보자.
이 상황에 놓인 간선들만을 모아서 새로이 트리를 구성해주면 이 문제는 골드 중반에 포진해있는 전형적인 트리 DP 문제로 바라볼 수 있게 된다.
이 문제에서는 최솟값이지만 최댓값을 구해야 하는 이 문제 가 비슷하지 않을까 싶다.
새롭게 구성된 트리에서 각 도시는 고양이가 배치되거나, 배치되지 않은 상태를 가질 수 있으므로 다음과 같이 정의해줄 수 있다.
이어서 $now → nxt$를 잇는 간선을 보고 있을 때 각 상태 전이는 다음 두가지로 분류할 수 있다.
각 도시에 대해 중복 배치가 일어나지 않도록 고양이 배치 여부를 기록할 배열을 함께 운용하는 것이 편하다. 자세한 흐름은 아래 코드를 참고하자.
전체 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | #include<bits/stdc++.h> #define N 100001 using namespace std; vector <vector <int>> dp(N, vector <int>(2, -1)); vector <int> Gr[N], Gr_[N]; int dep[N], flag[N]; int n, k, pdp[N][18]; void Input() { ios_base::sync_with_stdio(0); cin.tie(0); cin >> n >> k; for(int i{}; ++i < n;) { int u, v; cin >> u >> v; Gr[u].push_back(v); Gr[v].push_back(u); } } void LCAInit(int now, int depth) { dep[now] = depth; for(int nxt : Gr[now]) if(!dep[nxt]) { pdp[nxt][0] = now; LCAInit(nxt, depth + 1); } } void TableDP() { for(int j(1); j < 18; j++) for(int i{}; i < n; i++) pdp[i][j] = pdp[pdp[i][j - 1]][j - 1]; } int Lifting(int u, int diff) { for(int i{}; diff; diff >>= 1, i++) if(diff & 1) u = pdp[u][i]; return u; } int getLCA(int u, int v) { u = Lifting(u, dep[u] - dep[v]); if(u == v) return u; for(int i(17); ~i; i--) if(pdp[u][i] ^ pdp[v][i]) { u = pdp[u][i]; v = pdp[v][i]; } return pdp[u][0]; } int coverDP(int prev, int now, int is) { auto& cur(dp[now][is]); if(~cur) return cur; cur = is; if(is) cur -= flag[now]; for(int nxt : Gr_[now]) if(nxt ^ prev) { if(is) cur += min(coverDP(now, nxt, 0), coverDP(now, nxt, 1)); else cur += coverDP(now, nxt, 1); } return cur; } void getAns(int ans = 0) { while(k--) { int u, v; cin >> u >> v; if(dep[u] < dep[v]) swap(u, v); int lca(getLCA(u, v)), dis(dep[u] + dep[v] - 2 * dep[lca]); int target(Lifting(u, dis >> 1)); if(dis & 1) { Gr_[target].push_back(pdp[target][0]); Gr_[pdp[target][0]].push_back(target); } else if(!flag[target]) { flag[target] = 1; ans++; } } for(int i{}; i < n; i++) { sort(Gr_[i].begin(), Gr_[i].end()); Gr_[i].erase(unique(Gr_[i].begin(), Gr_[i].end()), Gr_[i].end()); } for(int i{}; i < n; i++) if(Gr_[i].size() && !~dp[i][0]) ans += min(coverDP(i, i, 0), coverDP(i, i, 1)); cout << ans; } int main() { Input(); LCAInit(0, 1); TableDP(); getAns(); } | cs |
comment
끝
'Problem Solving > Baekjoon Online Judge (Platinum)' 카테고리의 다른 글
백준 31222 - 수열과 어렵지 않은 쿼리 (C++) (0) | 2024.08.27 |
---|---|
백준 30976 - 사랑의 큐피드 (C++) (0) | 2023.12.24 |
백준 30975 - 약간 모자라지만 착한 친구야 (C++) (0) | 2023.12.24 |
백준 30943 - Zatopljenje (C++) (1) | 2023.12.11 |
백준 30512 - 조용히 완전히 영원히 (C++) (1) | 2023.11.20 |