ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 백준 1010번 다리 놓기
    알고리즘 문제풀이/Java 2020. 4. 6. 22:38

     

     

    문제

    재원이는 한 도시의 시장이 되었다. 이 도시에는 도시를 동쪽과 서쪽으로 나누는 큰 강이 흐르고 있다. 하지만 재원이는 다리가 없어서 시민들이 강을 건너는데 큰 불편을 겪고 있음을 알고 다리를 짓기로 결심하였다. 강 주변에서 다리를 짓기에 적합한 곳을 사이트라고 한다. 재원이는 강 주변을 면밀히 조사해 본 결과 강의 서쪽에는 N개의 사이트가 있고 동쪽에는 M개의 사이트가 있다는 것을 알았다. (N ≤ M)

    재원이는 서쪽의 사이트와 동쪽의 사이트를 다리로 연결하려고 한다. (이때 한 사이트에는 최대 한 개의 다리만 연결될 수 있다.) 재원이는 다리를 최대한 많이 지으려고 하기 때문에 서쪽의 사이트 개수만큼 (N개) 다리를 지으려고 한다. 다리끼리는 서로 겹쳐질 수 없다고 할 때 다리를 지을 수 있는 경우의 수를 구하는 프로그램을 작성하라.

    입력

    입력의 첫 줄에는 테스트 케이스의 개수 T가 주어진다. 그 다음 줄부터 각각의 테스트케이스에 대해 강의 서쪽과 동쪽에 있는 사이트의 개수 정수 N, M (0 < N ≤ M < 30)이 주어진다.

    출력

    각 테스트 케이스에 대해 주어진 조건하에 다리를 지을 수 있는 경우의 수를 출력한다.

    예제 입력 1 복사

    3 2 2 1 5 13 29

    예제 출력 1 복사

    1 5 67863915


    해설

     

    참고로 아래 코드는 위 문제에 대한 답은 아니다. 입력과 출력의 방식만 바꾸면 답이 될 수 있다.

    아래 코드는 n개의 원소 중 m개를 고르는 모든 조합을 찾는 알고리즘인데, 위 다리놓기가 바로 그 문제이다.

    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            //BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(System.out));
            //String line = br.readLine();
            List<Integer> picked = new ArrayList<>();
            pick(7, 4, picked);
        }
    
        public static void pick(int n, int toPick, List<Integer> picked){
            // n : 원소들의 총 개수
            // toPick : 더 골라야 할 원소들의 개수
            // picked : 지금까지 고른 원소들의 번호
            if(toPick == 0) {
                for(int num : picked)
                    System.out.print(num);
                System.out.println("");
                return;
            }
            int size = picked.size();
            for(int i = picked.isEmpty() ? 0 : picked.get(size - 1) + 1; i < n - toPick + 1; ++i){
                picked.add(i);
                pick(n, toPick - 1, picked);
                picked.remove(size);
            }
        }
    }
    

     

    위 문제에 대한 풀이는 나중에 다시 달아놓도록 하겠다.

     


    import java.io.*;
    import java.math.BigInteger;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            int testCase = Integer.parseInt(br.readLine());
            while(testCase-- > 0){
                String[] line = br.readLine().split(" ");
                BigInteger k = new BigInteger(line[0]); // 뽑는 경우
                BigInteger n = new BigInteger(line[1]); // 전체의 경우
                // 공식 : n! / (n - k)!k!
                BigInteger ret = fac(n).divide(fac(n.subtract(k)).multiply(fac(k)));
    
                System.out.println(ret);
            }
        }
        public static BigInteger fac(BigInteger n){
            if(n.equals(BigInteger.ZERO))
                return BigInteger.ONE;
            return n.multiply(fac(n.subtract(BigInteger.ONE)));
        }
    }

     

    공식을 이용하여 문제를 해결했는데, 이 과정에서 BigInteger이라는 클래스를 이용하였다. 다른 답을 보면 대부분 숫자의 크기때문에 배열을 통해서 문제를 해결하였는데, 다음과 같다. 

     

    import java.io.*;
    
    public class Main {
        public static void main(String[] args) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            int testCase = Integer.parseInt(br.readLine());
            int[][] dp = new int[31][31];
            for(int i = 1; i < 31; i++){
                for(int j = 0; j <= i; j++){
                    if(j == 0 || i == j ){
                        dp[i][j] = 1;
                    } else {
                        dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                    }
                }
            }
            while(testCase-- > 0){
                String[] line = br.readLine().split(" ");
                int k = Integer.parseInt(line[0]); // 뽑는 경우
                int n = Integer.parseInt(line[1]); // 전체의 경우
    
                System.out.println(dp[n][k]);
            }
        }
    
    }

     

    확실히 시간측면에서 큰 이득을 볼 수 있다. 앞으로 조합의 문제에서 잘 이용하도록 하자.

    느낀 점

    • 순열과 조합에 관한 문제였다. 이 부분에 대해 개념이 많이 취약해졌으니 공부할 필요가 있다.
    • 처음 저 문제를 봤을 때, 느낌 상 공식이 존재할 거 같다고는 생각했지만, 이 문제가 조합일 줄은 생각도 못했다. 가만 생각해보면, 임의의 다리 네 개를 아무렇게나 놓아도 다시 재배치하면 겹치지 않게 놓을 수가 있다. 이 말은 조합의 정의인 순서를 고려하지 않는 것과 동일하다. 앞으로 순열과 조합 문제가 나오면 이 문제가 순열인지, 조합인지를 분명히 파악하는 습관이 필요할 듯 하다.

     

     

     

    '알고리즘 문제풀이 > Java' 카테고리의 다른 글

    문제풀이 노트  (0) 2020.04.07
    알고스팟 Boggle  (0) 2020.04.06
    백준 1152번 단어의 개수  (0) 2020.04.05
    백준 1233번 주사위  (0) 2020.04.05
    백준 1076번 저항  (0) 2020.04.05

    댓글

Designed by Tistory.