CodingTest/Java

BOJ_9466 : ν…€ ν”„λ‘œμ νŠΈ

Soom_1n 2024. 5. 23. 17:48

πŸ‘‰ 문제링크



πŸ”Έ 문제 뢄석 πŸ”Έ

  • T번의 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ•ˆμ—μ„œ λ‹€μŒκ³Ό 같은 μž…λ ₯이 주어진닀.
  • nλͺ… (2~10만)의 학생 μˆ˜κ°€ 주어지고, λ‹€μŒ 쀄에 각 학생이 같이 νŒ€μ„ ν•˜κ³  싢은 ν•™μƒμ˜ λ²ˆν˜Έκ°€ 주어진닀.
  • 자기 μžμ‹ μ„ λ½‘κ±°λ‚˜, νŒ€μ„ ν•˜κ³ μ‹Άμ€ 학생듀끼리 μˆœν™˜κ΅¬μ‘°(μ„œν΄)이 λ§Œλ“€μ–΄μ§€λ©΄ νŒ€μ„ λ§Œλ“€ 수 μžˆλ‹€.
  • μ΅œμ’…μ μœΌλ‘œ νŒ€μ— μ†ν•˜μ§€ μ•Šμ€ ν•™μƒμ˜ 수λ₯Ό 좜λ ₯ν•œλ‹€.

πŸ”Έ 문제 풀이 πŸ”Έ

  1. μ œν•œμ‹œκ°„ 3μ΄ˆμ— 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ˜ n이 10만이기 λ•Œλ¬Έμ— O(n^2)의 μ•Œκ³ λ¦¬μ¦˜μ€ μ‚¬μš©ν•  수 μ—†λ‹€.
  2. νŒ€μ΄ λ§Œλ“€μ–΄μ§€λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ 깊이 μš°μ„  탐색(DFS)λ₯Ό μ‚¬μš©ν•œλ‹€.
  3. DFS의 경우의 μˆ˜λŠ” λ‹€μŒκ³Ό κ°™λ‹€.
    1. 1) νƒμƒ‰ν•˜λ‹€κ°€ μ‹œμž‘ 번호λ₯Ό λ‹€μ‹œ 찾음 : νƒμƒ‰ν•œ λͺ¨λ“  학생듀은 같은 νŒ€
    2. 2) νƒμƒ‰ν•˜λ‹€κ°€ 쀑간 번호λ₯Ό λ‹€μ‹œ 찾음 : 쀑간 λ²ˆν˜ΈκΉŒμ§€ νŒ€μ΄κ³ , μ‹œμž‘ λ²ˆν˜ΈλΆ€ν„° 쀑간 번호 μ‚¬μ΄λŠ” νŒ€μ„ λ§Œλ“€ 수 μ—†μŒ
    3. 3) 이미 νŒ€ μ†Œμ†μ΄ μžˆλŠ” 번호 : μ‹œμž‘ λ²ˆν˜ΈλΆ€ν„° ν˜„μž¬κΉŒμ§€ λͺ¨λ‘ νŒ€μ„ λ§Œλ“€ 수 μ—†λŠ” 학생 번호
  4. nλͺ…μ˜ 학생듀이 λͺ¨λ‘ 번호λ₯Ό 가리킀고 μžˆμœΌλ―€λ‘œ, DFS 탐색은 항상 μœ„ 3가지 경우의 끝을 보게 λœλ‹€. 그리고 1번과 2λ²ˆμ€ 사싀상 같은 경우라고 λ³Ό 수 μžˆλ‹€.
  5. μ€‘μš”ν•œκ±΄ ν•œλ²ˆ νƒμƒ‰ν•œ λ²ˆν˜ΈλŠ” λ‹€μ‹œ DFSλ₯Ό νƒμƒ‰ν•˜μ§€ μ•Šκ²Œν•΄μ„œ O(n)의 μ‹œκ°„ λ³΅μž‘λ„λ₯Ό λ§Œλ“œλŠ” 것이닀.

πŸ”Έ μ½”λ“œ πŸ”Έ

import java.io.*;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

    private static int[] pick, visited;
    private static boolean[] isTeam;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        StringTokenizer st;
        StringBuilder sb = new StringBuilder();

        int T = Integer.parseInt(br.readLine());

        for (int t = 0; t < T; t++) {
            int n = Integer.parseInt(br.readLine());
            pick = new int[n];

            st = new StringTokenizer(br.readLine());
            for (int i = 0; i < n; i++) {
                pick[i] = Integer.parseInt(st.nextToken()) - 1;
            }

            int cnt = 0; // νŒ€μ΄ μ—†λŠ” μ• λ“€ 수
            isTeam = new boolean[n]; // νŒ€ μ†Œμ† μ—¬λΆ€
            visited = new int[n]; // 방문 체크
            Arrays.fill(visited, -1);
            for (int i = 0; i < n; i++) {
                if (visited[i] == -1 && !isTeam[i]) { // λ°©λ¬Έ μ•ˆν–ˆκ³ , νŒ€ μ—†μœΌλ©΄ dfs 돌림
                    dfs(i, i);
                }
                if (!isTeam[i]) { // dfs 후에도 νŒ€ μ—†μœΌλ©΄ 카운트
                    cnt++;
                }
            }
            sb.append(cnt).append('\n');
        }
        bw.write(sb.toString());
        bw.flush();
    }

    private static int dfs(int now, int start) {
        // λ°©λ¬Έ ν–ˆμ—ˆμŒ
        if (visited[now] > -1) {
            if (visited[now] == start) { // ν˜„μž¬ 탐색에 방문함 : μ„œν΄ λ°œμƒ
                isTeam[now] = true;
                return now;
            } else { // νŒ€μ΄ 있음 or 이전 λ°©λ¬Έ
                return -1;
            }
        }

        visited[now] = start;

        // μž¬κ·€
        int result = dfs(pick[now], start);

        if (result > -1 && now != result) { // ν˜„μž¬ μ„œν΄ μ•ˆμͺ½μž„
            isTeam[now] = true;
            return result;
        }

        // μž¬κ·€ λ˜λŒμ•„ κ°€λ©° 처리 ν•  κ±° μ—†μŒ, μ„œν΄μ’…λ£Œ
        return -1;
    }
}

πŸ”Έ μ½”λ“œ 해석 πŸ”Έ

  • 학생 별 선택 번호λ₯Ό μ €μž₯ν•˜λŠ” int λ°°μ—΄ pick, νŒ€ μ†Œμ†μ΄ μžˆμŒμ„ λ‚˜νƒ€λ‚΄λŠ” boolean λ°°μ—΄ isTeam, 탐색 μ‹œ μ‹œμž‘ 번호λ₯Ό μ €μž₯ν•˜λŠ” intλ°°μ—΄ visitedλ₯Ό μ‚¬μš©ν–ˆλ‹€.
  • n개의 λ²ˆν˜Έμ— λ‹€μŒκ³Ό 같은 계산을 λ°˜λ³΅ν•œλ‹€.
    • λ°©λ¬Έν•˜μ§€ μ•Šμ•˜κ³ , νŒ€μ΄ μ—†μœΌλ©΄ dfs() λ©”μ„œλ“œλ₯Ό λŒλ¦°λ‹€.
    • 이후에도 νŒ€μ΄ μ—†μœΌλ©΄ cntλ₯Ό +1 ν•œλ‹€.
  • dfs μž¬κ·€ λ©”μ„œλ“œμ—μ„œλŠ” λ‹€μŒκ³Ό 같은 계산을 λ°˜λ³΅ν•œλ‹€.
    • λ°©λ¬Έ 확인
      • ν˜„μž¬ 탐색에 λ°©λ¬Έν–ˆλ˜ 번호라면, μ„œν΄μ΄ λ°œμƒν•œ κ²ƒμ΄λ―€λ‘œ, λ‹€μ‹œ ν˜„μž¬ λ²ˆν˜Έκ°€ λ‚˜μ˜¬ λ•Œ κΉŒμ§€ isTeam[now]을 true둜 μ –μ•„ν•˜κ³  nowλ₯Ό λ°˜ν™˜ν•œλ‹€.
      • 이전 탐색 λ°©λ¬Έν–ˆλ˜ 번호라면, μ„œν΄μ΄ λ§Œλ“€μ–΄μ§€μ§€ μ•ŠμœΌλ―€λ‘œ -1을 λ°˜ν™˜ν•œλ‹€.
    • λ°©λ¬Έν•˜μ§€ μ•Šμ•˜λ˜ 번호라면 λ°©λ¬Έμ²˜λ¦¬ν•œλ‹€.
    • μž¬κ·€ κ²°κ³Ό 처리
      • dfs(pick[now], start) 둜 λ‹€μ‹œ μž¬κ·€ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.
      • λ§Œμ•½ μž¬κ·€ λ©”μ„œλ“œμ˜ λ°˜ν™˜κ°’ resultκ°€ 0이상이고 now와 λ‹€λ₯΄λ‹€λ©΄, μ„œν΄ μ€‘κ°„μ΄λ―€λ‘œ istTeam[now]λ₯Ό true둜 λ°”κΏ”μ£Όκ³  resultλ₯Ό λ°˜ν™˜ν•œλ‹€.
    • resultκ°€ -1μ΄κ±°λ‚˜ result == now라면 μ„œν΄μ΄ μ’…λ£Œλœ κ²ƒμ΄λ―€λ‘œ -1을 λ°˜ν™˜ν•œλ‹€.

πŸ”Έ end πŸ”Έ

  •  λ‹¨μˆœν•œ DFS μ•Œκ³ λ¦¬μ¦˜λ§Œ ν•„μš”ν•œ 문제인데, λ‚œμ΄λ„κ°€ κ½€ λ†’μ•˜λ˜ 것 κ°™λ‹€.
    • 각 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μ‹€ν–‰μ˜ μ‹œκ°„ λ³΅μž‘λ„λ₯Ό O(n)으둜 ν•΄μ•ΌλΌμ„œ 1회 μ‹œκ°„ 초과 났닀.
    • 2번째 제좜의 ν‹€λ ΈμŠ΅λ‹ˆλ‹€λŠ” 이전 νƒμƒ‰μ—μ„œ νŒ€μ΄ μ—†μŒμ„ μ•Œμ•˜λŠ”λ°, ν˜„μž¬ νƒμƒ‰μ—μ„œ λ‹€μ‹œ λ°©λ¬Έν•΄μ•Ό ν•  κ²½μš°μ— 였λ₯˜κ°€ λ‚˜μ„œ ν‹€λ Έλ‹€. visitedλ₯Ό μ²˜μŒμ—” boolean λ°°μ—΄λ‘œ λ’€λ‹€κ°€, 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ startλ₯Ό κΈ°λ‘ν•˜κ³  visitedλ₯Ό intλ°°μ—΄λ‘œ λ°”κΏ”μ„œ μ²˜λ¦¬ν–ˆλ‹€.
  • κ΅¬ν˜„ν•˜λŠ” 방법이 λ‹€μ–‘ν•˜κ²Œ μžˆλŠ” 문제인 것 κ°™λ‹€.
    • ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό λ‹€μ–‘ν•˜κ²Œ λŒλ €λ³΄λ©΄μ„œ 디버깅 ν•˜λŠ”κ²Œ μ€‘μš”ν•œ 것 κ°™μ•„μ„œ, 직접 λ§Œλ“€κΈ°λ„ ν•˜κ³  질문 κ²Œμ‹œνŒμ— μžˆλŠ” λ°˜λ‘€λ“€μ„ λͺ¨μœΌκΈ°λ„ ν–ˆλ‹€.
    • ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ λͺ¨μŒμ„ 질문 κ²Œμ‹œνŒμ— λ‚¨κ²¨λ†“μ•˜λ‹€.
728x90