μ•Œκ³ λ¦¬μ¦˜/BOJ - Java

[BOJ/Java] 5266. Strange Dream

ImJay 2025. 3. 13. 17:12
λ°˜μ‘ν˜•

πŸ“Œ [BOJ/Java] 5266. Strange Dream

πŸ”— 문제 링크: 문제 보기


πŸ“– 문제 해석

Dumitruκ°€ μ΄μƒν•œ κΏˆμ„ κΏ¨λ‹€. κΏˆμ†μ—μ„œ 문이 잠긴 방에 κ°‡νžŒ DumitruλŠ” n개의 λ°•μŠ€λ₯Ό λ°œκ²¬ν–ˆλ‹€. 각 λ°•μŠ€μ—λŠ” m개의 μ ‘μ‹œκ°€ μžˆμ—ˆκ³ , 각 μ ‘μ‹œμ—λŠ” 1 μ΄μƒμ˜ μ •μˆ˜κ°€ μ ν˜€ μžˆμ—ˆλ‹€. 그러던 쀑 두 개의 μ •μˆ˜ k와 l이 적힌 μͺ½μ§€λ₯Ό λ°œκ²¬ν–ˆλ‹€. μͺ½μ§€μ—λŠ” λ‹€μŒκ³Ό 같은 μž‘μ—…μ„ ν•˜λΌκ³  λ˜μ–΄ μžˆμ—ˆλ‹€.

 

1단계: 첫 번째 λ°•μŠ€λΆ€ν„° n번째 λ°•μŠ€κΉŒμ§€ μˆœμ„œλŒ€λ‘œ 각 λ°•μŠ€μ—μ„œ ν•œ 개의 μ ‘μ‹œλ₯Ό μ„ νƒν•œλ‹€. μ„ νƒν•œ μ ‘μ‹œμ˜ 숫자λ₯Ό λ…ΈνŠΈμ— κΈ°λ‘ν•œ ν›„, ν•΄λ‹Ή μ ‘μ‹œμ˜ 숫자λ₯Ό 1둜 λ°”κΎΌλ‹€.

2단계: n-1번째 λ°•μŠ€λΆ€ν„° 두 번째 λ°•μŠ€κΉŒμ§€ μ—­μˆœμœΌλ‘œ 각 λ°•μŠ€μ—μ„œ ν•œ 개의 μ ‘μ‹œλ₯Ό μ„ νƒν•œλ‹€.

λͺ©ν‘œλŠ” λ…ΈνŠΈμ— 기둝된 μˆ«μžλ“€μ˜ 곱이 k둜 λ‚˜λˆ„μ–΄ 떨어지도둝 ν•˜λŠ” 선택 λ°©λ²•μ˜ 총 κ°€μ§“μˆ˜ Tλ₯Ό κ΅¬ν•˜κ³ , Tλ₯Ό l둜 λ‚˜λˆˆ λ‚˜λ¨Έμ§€λ₯Ό 좜λ ₯ν•˜λŠ” 것이닀.

 

βœ… 핡심 κ°œλ…

  • μ •μˆ˜λ‘ κ³Ό gcdλ₯Ό ν™œμš©ν•΄ k의 μ•½μˆ˜λ₯Ό μ²˜λ¦¬ν•œλ‹€.
  • 각 λ°•μŠ€λ³„ μ ‘μ‹œ μ„ νƒμ˜ 경우의 수λ₯Ό ꡬ뢄해 κ³„μ‚°ν•œλ‹€.
  • 동적 κ³„νšλ²•(DP)을 톡해 μƒνƒœ 전이λ₯Ό λˆ„μ ν•œλ‹€.

πŸ› οΈ 풀이 κ³Όμ •

λ¨Όμ € μž…λ ₯으둜 n, m, k, lκ³Ό 각 λ°•μŠ€μ˜ μ ‘μ‹œμ— 적힌 수λ₯Ό λ°›μ•˜λ‹€. k의 λͺ¨λ“  μ•½μˆ˜λ₯Ό ꡬ해 각 μ•½μˆ˜λ₯Ό μƒνƒœλ‘œ μ‚¬μš©ν•˜κ³ , μ•½μˆ˜μ— λŒ€ν•œ 인덱슀λ₯Ό λ§€ν•‘ν–ˆλ‹€.

 

각 λ°•μŠ€λ§ˆλ‹€ μ ‘μ‹œ μ„ νƒμ˜ 경우의 수λ₯Ό κ³„μ‚°ν–ˆλ‹€. 첫 번째 λ°•μŠ€μ™€ λ§ˆμ§€λ§‰ λ°•μŠ€λŠ” μ ‘μ‹œλ₯Ό ν•œ 번만 μ„ νƒν•˜λ―€λ‘œ 각 μ ‘μ‹œκ°€ κΈ°μ—¬ν•˜λŠ” 값이 κ·ΈλŒ€λ‘œ λ°˜μ˜λ˜μ—ˆλ‹€.

 

쀑간 λ°•μŠ€λŠ” 두 번 μ„ νƒν•˜λŠ” κ²½μš°κ°€ μžˆμ–΄, 같은 μ ‘μ‹œλ₯Ό 두 번 μ„ νƒν•˜λŠ” 경우(두 번째 선택 μ‹œ 1이 기둝됨)와 μ„œλ‘œ λ‹€λ₯Έ μ ‘μ‹œλ₯Ό μ„ νƒν•˜λŠ” 경우λ₯Ό λͺ¨λ‘ κ³ λ €ν–ˆλ‹€.

 

DP 배열을 μ΄μš©ν•΄ 각 λ°•μŠ€μ—μ„œ κ³„μ‚°λœ 경우의 수λ₯Ό λˆ„μ ν•˜λ©° μƒνƒœ 전이λ₯Ό μ§„ν–‰ν–ˆλ‹€. ν˜„μž¬ μƒνƒœλŠ” μ§€κΈˆκΉŒμ§€ μ„ νƒν•œ μ ‘μ‹œλ“€μ˜ 숫자 곱의 gcd(k, ·) κ°’μœΌλ‘œ ν‘œν˜„λœλ‹€.

 

λͺ¨λ“  λ°•μŠ€λ₯Ό μ²˜λ¦¬ν•œ ν›„, μ΅œμ’… μƒνƒœκ°€ k둜 λ‚˜λˆ„μ–΄ λ–¨μ–΄μ§€λŠ” 경우의 수λ₯Ό l둜 λ‚˜λˆˆ λ‚˜λ¨Έμ§€λ₯Ό 좜λ ₯ν–ˆλ‹€.


πŸ’» μ½”λ“œ κ΅¬ν˜„ (Java)

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

public class Main {
    public static void main(String[] args) throws Exception {
        // μž…λ ₯을 μœ„ν•œ BufferedReader μ΄ˆκΈ°ν™”
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        // 첫 번째 쀄: n (λ°•μŠ€ 개수), m (각 λ°•μŠ€ λ‹Ή μ ‘μ‹œ 개수) μž…λ ₯
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());
        
        // 두 번째 쀄: k (λͺ©ν‘œ 배수), mod (λ‚˜λ¨Έμ§€ μ—°μ‚° λͺ¨λ“ˆ) μž…λ ₯
        st = new StringTokenizer(br.readLine());
        int k = Integer.parseInt(st.nextToken());
        int mod = Integer.parseInt(st.nextToken());
        
        // k의 λͺ¨λ“  μ•½μˆ˜λ₯Ό ꡬ함 (각 μƒνƒœλ₯Ό μ•½μˆ˜λ‘œ μ‚¬μš©)
        ArrayList<Integer> dlist = new ArrayList<>();
        for (int i = 1; i * i <= k; i++) {
            if (k % i == 0) {
                dlist.add(i);
                if (i * i != k)
                    dlist.add(k / i);
            }
        }
        // μ•½μˆ˜λ₯Ό μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬
        Collections.sort(dlist);
        int dCount = dlist.size();
        int[] dArr = new int[dCount];
        for (int i = 0; i < dCount; i++) {
            dArr[i] = dlist.get(i);
        }
        
        // 각 μ•½μˆ˜μ— 인덱슀λ₯Ό λ§€ν•‘ (μƒνƒœ 전이 ν…Œμ΄λΈ” ꡬ성에 μ‚¬μš©)
        HashMap<Integer, Integer> dIndex = new HashMap<>();
        for (int i = 0; i < dCount; i++) {
            dIndex.put(dArr[i], i);
        }
        
        // 두 μƒνƒœμ˜ κ³±μ…ˆ 결과의 gcd(k, κ³±)으둜 μ „μ΄λ˜λŠ” μƒνƒœλ₯Ό 미리 계산 (μƒνƒœ 전이 ν…Œμ΄λΈ”)
        int[][] mult = new int[dCount][dCount];
        for (int i = 0; i < dCount; i++) {
            for (int j = 0; j < dCount; j++) {
                long prod = (long) dArr[i] * dArr[j];
                int g = (int) gcd(prod, k);
                mult[i][j] = dIndex.get(g);
            }
        }
        
        // dp λ°°μ—΄ μ΄ˆκΈ°ν™” (ν˜„μž¬ μƒνƒœλ³„ 경우의 수 μ €μž₯)
        int[] dp = new int[dCount];
        // 초기 μƒνƒœλŠ” 1 (곱의 κ²°κ³Όκ°€ 1)인 μƒνƒœμ—μ„œ μ‹œμž‘
        dp[dIndex.get(1)] = 1;
        
        // 각 λ°•μŠ€λ³„λ‘œ 처리 (총 n개의 λ°•μŠ€)
        for (int box = 1; box <= n; box++) {
            // 각 λ°•μŠ€ λ‚΄μ—μ„œ μ•½μˆ˜ μƒνƒœμ— ν•΄λ‹Ήν•˜λŠ” μ ‘μ‹œ 개수 카운트
            int[] freq = new int[dCount];
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < m; j++) {
                int val = Integer.parseInt(st.nextToken());
                int g = (int) gcd(val, k);
                int idx = dIndex.get(g);
                freq[idx]++; // ν•΄λ‹Ή μ•½μˆ˜ μƒνƒœμ— ν•΄λ‹Ήν•˜λŠ” μ ‘μ‹œ 개수 증가
            }
            
            // ν˜„μž¬ λ°•μŠ€μ—μ„œ 선택할 경우의 수λ₯Ό 계산할 λ°°μ—΄
            int[] boxDist = new int[dCount];
            if (box == 1 || box == n) {
                // 첫 번째 λ°•μŠ€μ™€ λ§ˆμ§€λ§‰ λ°•μŠ€λŠ” ν•œ 번만 μ„ νƒν•˜λ―€λ‘œ κ·ΈλŒ€λ‘œ μ‚¬μš©
                for (int i = 0; i < dCount; i++) {
                    boxDist[i] = freq[i] % mod;
                }
            } else {
                // 쀑간 λ°•μŠ€λŠ” 두 번 선택함 (ν•œ λ²ˆμ€ 기둝 ν›„ 1둜 λ³€κ²½, 두 번 선택 μ‹œ 곱에 영ν–₯ μ—†μŒ)
                int[] doubleDist = new int[dCount];
                // λ¨Όμ € ν•œ 번 μ„ νƒν•˜λŠ” 경우 (기본적으둜 freq 값을 더함)
                for (int i = 0; i < dCount; i++) {
                    doubleDist[i] = (doubleDist[i] + freq[i]) % mod;
                }
                // 두 번째 μ„ νƒμ˜ 경우: 같은 μ ‘μ‹œ λ˜λŠ” λ‹€λ₯Έ μ ‘μ‹œ 선택 경우 κ³ λ €
                for (int i = 0; i < dCount; i++) {
                    for (int j = 0; j < dCount; j++) {
                        // i와 jκ°€ 같은 경우: 같은 μ ‘μ‹œλ₯Ό 두 번 μ„ νƒν•˜λŠ” 경우
                        // i와 jκ°€ λ‹€λ₯Έ 경우: μ„œλ‘œ λ‹€λ₯Έ μ ‘μ‹œλ₯Ό μ„ νƒν•˜λŠ” 경우
                        long ways = (i == j) ? (long) freq[i] * (freq[i] - 1) : (long) freq[i] * freq[j];
                        if (ways == 0)
                            continue;
                        int idx = mult[i][j];
                        doubleDist[idx] = (int) ((doubleDist[idx] + ways) % mod);
                    }
                }
                boxDist = doubleDist;
            }
            
            // ν˜„μž¬κΉŒμ§€μ˜ dp와 이번 λ°•μŠ€μ˜ boxDistλ₯Ό μ΄μš©ν•΄ λ‹€μŒ μƒνƒœ(ndp)λ₯Ό 계산
            int[] ndp = new int[dCount];
            for (int i = 0; i < dCount; i++) {
                if (dp[i] == 0)
                    continue;
                for (int j = 0; j < dCount; j++) {
                    if (boxDist[j] == 0)
                        continue;
                    int ns = mult[i][j]; // ν˜„μž¬ μƒνƒœ iμ—μ„œ λ°•μŠ€μ˜ 선택 jλ₯Ό μ μš©ν•œ μƒˆλ‘œμš΄ μƒνƒœ
                    ndp[ns] = (int) ((ndp[ns] + (long) dp[i] * boxDist[j]) % mod);
                }
            }
            // dp λ°°μ—΄ κ°±μ‹ 
            dp = ndp;
        }
        // μ΅œμ’…μ μœΌλ‘œ dp λ°°μ—΄μ—μ„œ λͺ©ν‘œ μƒνƒœ k (즉, μ•½μˆ˜ μƒνƒœκ°€ kκ°€ λ˜λŠ” 경우)의 값을 좜λ ₯
        System.out.println(dp[dIndex.get(k)] % mod);
    }
    
    // μœ ν΄λ¦¬λ“œ ν˜Έμ œλ²•μ„ μ΄μš©ν•œ μ΅œλŒ€κ³΅μ•½μˆ˜(GCD) 계산 λ©”μ†Œλ“œ
    static long gcd(long a, long b) {
        while (b != 0) {
            long t = a % b;
            a = b;
            b = t;
        }
        return a;
    }
}

⏱️ μ‹œκ°„ λ³΅μž‘λ„ 뢄석

λ¬Έμ œλŠ” 각 λ°•μŠ€λ§ˆλ‹€ m개의 μ ‘μ‹œλ₯Ό μ²˜λ¦¬ν•˜κ³ , k의 μ•½μˆ˜ 개수λ₯Ό d라 ν•  λ•Œ O(n*m + n*d²)의 μ‹œκ°„ λ³΅μž‘λ„λ₯Ό κ°€μ§„λ‹€.
n은 μ΅œλŒ€ 200, m은 μ΅œλŒ€ 10,000이며, dλŠ” k의 μ•½μˆ˜ 개수둜 보톡 √k에 κ°€κΉŒμ›Œ μ œν•œ λ‚΄μ—μ„œ μΆ©λΆ„νžˆ κ³„μ‚°λœλ‹€.


πŸ’‘ λŠλ‚€μ 

문제 μ ‘κ·Ό 방식은 각 λ°•μŠ€μ—μ„œμ˜ μ ‘μ‹œ 선택에 λ”°λ₯Έ μƒνƒœ 전이λ₯Ό λͺ…ν™•ν•˜κ²Œ μ²˜λ¦¬ν•˜λŠ” 데 μ΄ˆμ μ„ λ‘μ—ˆλ‹€.
μ •μˆ˜λ‘ κ³Ό gcdλ₯Ό ν™œμš©ν•΄ k의 μ•½μˆ˜λ₯Ό μ²˜λ¦¬ν•˜λ©° μƒνƒœ 전이 ν…Œμ΄λΈ”μ„ κ΅¬μ„±ν•œ 점이 인상적이닀.
λ™μΌν•œ λ°•μŠ€μ—μ„œ μ ‘μ‹œλ₯Ό 두 번 μ„ νƒν•˜λŠ” 경우λ₯Ό μ„Έλ°€ν•˜κ²Œ κ³ λ €ν•œ 점이 문제 ν•΄κ²°μ˜ 핡심 ν¬μΈνŠΈμ˜€λ‹€.

λ°˜μ‘ν˜•