12월 27일 - 트리 [BOJ/13309]

Yullgiii·2024년 12월 26일
0

트리 경로 연결 여부와 에지 제거 문제

문제 설명

주어진 트리 구조에서 특정 두 정점을 연결하는 경로가 존재하는지를 확인하고, 필요에 따라 특정 에지를 제거하는 작업을 수행해야 한다. 트리는 루트가 (1)인 연결 그래프이고, 에지를 제거하면서도 효율적으로 경로 존재 여부를 확인하고 작업을 처리해야 한다.

질의는 두 가지 유형이다:
1. 두 정점을 연결하는 경로가 존재하는지 확인 ((d = 0)).
2. 경로 존재 여부를 확인하고, 존재 여부에 따라 특정 에지를 제거 ((d = 1)).


핵심 아이디어

  1. 트리의 구조 표현:

    • 각 정점의 부모를 기반으로 트리를 표현하고, 에지 정보를 유지한다.
  2. Heavy-Light Decomposition (HLD):

    • 트리를 체인으로 분리하여 경로 질의를 효율적으로 수행한다.
    • HLD로 트리를 분리한 후, 각 체인의 연결 여부를 세그먼트 트리로 관리한다.
  3. 세그먼트 트리:

    • 각 체인에서 에지의 활성 상태를 관리하며 경로 연결 여부를 확인한다.
    • 세그먼트 트리를 사용해 특정 범위의 모든 에지가 활성 상태인지 효율적으로 확인.
  4. 질의 처리:

    • 두 정점의 공통 조상을 기준으로 경로를 나누어 질의한다.
    • 경로 존재 여부에 따라 에지를 제거하거나 답을 출력.

코드

import java.io.*;
import java.util.*;

public class Main {
    static final int MAX = 200001;

    static int N, Q;
    static List<Integer>[] adj;
    static int[] subtreeSize, HLDNum, HLDHead, HLDDepth, HLDParent;
    static int[] tree;
    static int HLDCount = 1;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));

        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        Q = Integer.parseInt(st.nextToken());

        adj = new ArrayList[MAX];
        for (int i = 0; i < MAX; i++) adj[i] = new ArrayList<>();

        subtreeSize = new int[MAX];
        HLDNum = new int[MAX];
        HLDHead = new int[MAX];
        HLDDepth = new int[MAX];
        HLDParent = new int[MAX];

        int treeHeight = (int) Math.ceil(Math.log(N) / Math.log(2));
        int treeSize = (1 << (treeHeight + 1));
        tree = new int[treeSize];
        Arrays.fill(tree, 1);

        for (int i = 1; i < N; i++) {
            int p = Integer.parseInt(br.readLine());
            adj[p].add(i + 1);
        }

        HLDHead[1] = 1;
        calcSubtreeSize(1);
        heavyLightDecomposition(1, 1);

        while (Q-- > 0) {
            st = new StringTokenizer(br.readLine());
            int b = Integer.parseInt(st.nextToken());
            int c = Integer.parseInt(st.nextToken());
            int d = Integer.parseInt(st.nextToken());

            boolean result = queryPath(b, c);

            pw.println(result ? "YES" : "NO");

            if (d == 1) {
                if (result) updateTree(1, 1, N, HLDNum[b], 0);
                else updateTree(1, 1, N, HLDNum[c], 0);
            }
        }

        pw.flush();
    }

    static void calcSubtreeSize(int node) {
        subtreeSize[node] = 1;
        for (int child : adj[node]) {
            calcSubtreeSize(child);
            subtreeSize[node] += subtreeSize[child];
            if (subtreeSize[child] > subtreeSize[adj[node].get(0)]) {
                Collections.swap(adj[node], 0, adj[node].indexOf(child));
            }
        }
    }

    static void heavyLightDecomposition(int node, int depth) {
        HLDNum[node] = HLDCount++;
        HLDDepth[node] = depth;

        for (int child : adj[node]) {
            if (child == adj[node].get(0)) {
                HLDHead[child] = HLDHead[node];
                heavyLightDecomposition(child, depth);
            } else {
                HLDHead[child] = child;
                heavyLightDecomposition(child, depth + 1);
            }
        }
    }

    static boolean queryPath(int u, int v) {
        while (HLDHead[u] != HLDHead[v]) {
            if (HLDDepth[HLDHead[u]] < HLDDepth[HLDHead[v]]) {
                int temp = u;
                u = v;
                v = temp;
            }
            if (queryTree(1, 1, N, HLDNum[HLDHead[u]], HLDNum[u]) == 0) return false;
            u = HLDParent[HLDHead[u]];
        }
        if (HLDNum[u] > HLDNum[v]) {
            int temp = u;
            u = v;
            v = temp;
        }
        return queryTree(1, 1, N, HLDNum[u] + 1, HLDNum[v]) == 1;
    }

    static void updateTree(int node, int start, int end, int index, int value) {
        if (index < start || index > end) return;
        if (start == end) {
            tree[node] = value;
            return;
        }

        int mid = (start + end) / 2;
        updateTree(node * 2, start, mid, index, value);
        updateTree(node * 2 + 1, mid + 1, end, index, value);
        tree[node] = tree[node * 2] & tree[node * 2 + 1];
    }

    static int queryTree(int node, int start, int end, int left, int right) {
        if (left > end || right < start) return 1;
        if (left <= start && end <= right) return tree[node];

        int mid = (start + end) / 2;
        return queryTree(node * 2, start, mid, left, right) &
               queryTree(node * 2 + 1, mid + 1, end, left, right);
    }
}

코드 설명

  1. DFS를 통한 트리 분할:
  • 서브트리 크기를 계산하고, HLD를 통해 체인을 구성한다.
  1. HLD를 활용한 경로 질의:
  • 두 정점의 경로를 나누어, 체인 단위로 경로의 연결 여부를 확인한다.
  1. 세그먼트 트리:
  • 체인의 에지 활성 상태를 관리하며, 경로 연결 여부를 효율적으로 확인한다.
  1. 질의 처리:
  • 질의 결과를 출력하고 필요 시 에지를 제거하여 트리를 업데이트한다.

So...

이 문제는 트리의 구조와 경로 질의를 효율적으로 처리하기 위해 HLD와 세그먼트 트리를 결합한 풀이를 요구한다. 질의 수와 트리 크기가 매우 크기 때문에, 최적화를 통해 효율적으로 문제를 해결하였다.

profile
공부하는 거북이의 성장일기 🐢

0개의 댓글