ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [백준 33582번] 건물 폭파
    문제 풀이 2025. 3. 24. 20:29

     

    https://www.acmicpc.net/problem/33582

     

    제2회 디미고 대회에서 내가 출제한 문제이기도 하고, 에디토리얼에 엄밀한 증명을 적어두지 못해서 여기에 해설을 적어보려고 한다.

     

     

     

    처음에는 에디토리얼과 똑같이 일자 트리로 접근해 볼 것이다.

     

    모든 $i$ $(1 \leq i \leq N-1)$에 대하여 $i$번 정점과 $i+1$번 정점이 연결되어 있는 일자 트리에서 모든 정점(건물)의 내구도를 0 이하로 만들기 위해서는 $l$번 정점과 $r$번 정점에 각각 $L$, $R$의 폭발을 일으키면 된다고 하자.

     

    예를 들어 $N=10$이고 $l=3, r=7, L=3, R=4$일 때가 최적해라고 가정해 보겠다.

    그러면 아래와 같이 그림으로 나타낼 수 있다.

    각 정점에 닿은 폭발 강도의 총합을 그림으로 나타내보면

    이렇게 된다.

     

    즉, 모든 $i$ $(1 \leq i \leq N)$에 대하여 $i$번 정점에 전해지는 폭발의 강도는 $\max(L − |l − i|, 0) + \max(R − |r − i|, 0)$이다.

     

    이번에는 $m$번 정점에 강도 $L+R$의 폭발을 일으키는 상황으로 가정해 보자.

     

    $m$을 $4 \leq m \leq 7$ 범위 내의 어디로 설정해도 폭발 강도의 총합이 이전의 경우를 훨씬 뛰어넘는다.

     

    위의 경우, 모든 $i$ $(1 \leq i \leq N)$에 대하여 $i$번 정점에 전해지는 폭발의 강도는 $L + R − |m − i|$가 되는데, $\max(L − |l − i|, 0) + \max(R − |r − i|, 0) \leq L + R − |m − i|$가 성립하는 $m$을 $l \leq m \leq r$ 범위 안에서 무조건 찾을 수 있음이 보장된다는 것을 알 수 있다.

     

    즉, 폭발을 일으키는 정점의 개수를 줄이는 것이 절대 손해가 아니므로, 하나의 정점에서 한 번의 폭발을 일으키는 것이 곧 최적해라는 사실을 파악할 수 있다.

     

    이제 이를 일반적인 형태의 트리로 확장시켜 볼 것이다.

     

    지금까지 설명한 대로 일자 트리 위에서 최적의 $m$ 위치를 구했다고 하자.

     

    이제 이 일자 트리(검정)에 정점(하양)을 여러 개 연결하여 일반적인 형태의 트리로 확장시켰다.

    일자 트리의 $i$ $(1 \leq i \leq N)$번 정점에 전해진 폭발 강도의 총합을 $B_i$라고 하면, 최적해(한 정점에서 한 번 폭발을 일으키는 경우)일 때 $B_1, B_2, \cdots, B_N$이 최댓값이 된다.

     

    $B_i$와 연결된 하얀색 정점에는 $B_i-($$i$번 정점까지의 거리$)$만큼의 폭발이 전해지므로 $B_1, B_2, \cdots, B_N$이 최대인 상태가 하얀색 정점에 전해지는 폭발의 강도 또한 최대인 상태이다.

     

    따라서 일반적인 형태의 트리로 확장하여도 최적해를 구하는 방법은 동일하다는 것을 알 수 있다.

     

     

     

    한 정점에서 한번 폭발을 일으키는 것이 최적이라는 것을 구했으니 이제 최적의 정점 $m$을 찾는 방법을 알아보자.

     

    $dp[i]$를 모든 건물을 무너뜨리기 위해 $i$번 정점에 일으켜야할 폭발의 최소 강도로, $d_{i,j}$ 를 $i$번 정점과 $j$번 정점 사이의 간선 개수로 정의하면 $dp[i] = \min(A_1+d_{i,1}, A_2+d_{i,2}, \cdots, A_N+d_{i,N})$이 성립한다.

     

    따라서 모든 $i$ $(1 \leq i \leq N)$에 대하여 $dp[i]$의 최솟값이 문제의 정답이다.

     

    탐색 시간을 줄이기 위해 전방향 트리 DP(트리 리루팅)를 사용하면 $O(N)$ 시간에 문제를 해결할 수 있다.

    #include<iostream>
    #include<vector>
    #include<algorithm>
    using namespace std;
    vector <int> v[100001] = {};
    int p[100001] = {}, dp[100001] = {}, f[100001][2] = {};
    void set(int num, int be) {
        dp[num] = p[num];
    
        for (int i = 0; i < v[num].size(); i++) if (v[num][i] != be) {
            set(v[num][i], num);
            dp[num] = max(dp[num], dp[v[num][i]] + 1);
            if (dp[v[num][i]] + 1 >= f[num][0]) {
                f[num][1] = f[num][0];
                f[num][0] = dp[v[num][i]] + 1;
            }
            else if (dp[v[num][i]] + 1 >= f[num][1]) {
                f[num][1] = dp[v[num][i]] + 1;
            }
        }
        return;
    }
    void dfs(int num, int be, int add) {
        if (add >= f[num][0]) {
            f[num][1] = f[num][0];
            f[num][0] = add;
        }
        else if (add >= f[num][1]) {
            f[num][1] = add;
        }
        dp[num] = max(dp[num], add);
        for (int i = 0; i < v[num].size(); i++) if (v[num][i] != be) {
            int plus;
            if (f[num][0] == dp[v[num][i]] + 1) plus = f[num][1];
            else plus = f[num][0];
    
            dfs(v[num][i], num, max(p[num] + 1, plus + 1));
        }
        return;
    }
    int main() {
        int n;
    
        cin >> n;
        for (int i = 1; i < n; i++) {
            int a, b;
    
            cin >> a >> b;
            v[a].push_back(b);
            v[b].push_back(a);
        }
        for (int i = 1; i <= n; i++) cin >> p[i];
        set(1, 0);
        dfs(1, 0, 0);
        int ans = 1e9 * 2;
        for (int i = 1; i <= n; i++) ans = min(ans, dp[i]);
        cout << ans;
    }

    '문제 풀이' 카테고리의 다른 글

    [백준 24115번] 地域 (Regions)  (0) 2025.03.24
Designed by Tistory.