www.acmicpc.net/problem/1208

 

1208번: 부분수열의 합 2

첫째 줄에 정수의 개수를 나타내는 N과 정수 S가 주어진다. (1 ≤ N ≤ 40, |S| ≤ 1,000,000) 둘째 줄에 N개의 정수가 빈 칸을 사이에 두고 주어진다. 주어지는 정수의 절댓값은 100,000을 넘지 않는다.

www.acmicpc.net

문제

N개의 정수로 이루어진 수열이 있을 때, 크기가 양수인 부분수열 중에서 그 수열의 원소를 다 더한 값이 S가 되는 경우의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 정수의 개수를 나타내는 N과 정수 S가 주어진다. (1 ≤ N ≤ 40, |S| ≤ 1,000,000) 둘째 줄에 N개의 정수가 빈 칸을 사이에 두고 주어진다. 주어지는 정수의 절댓값은 100,000을 넘지 않는다.

출력

첫째 줄에 합이 S가 되는 부분수열의 개수를 출력한다.

예제 입력 1

5 0

-7 -3 -2 5 8

예제 출력 1

1

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[] arr = null;
    static ArrayList<Integer> first = null;
    static ArrayList<Integer> second = null;
    static int N, S, ans;

    public static int lowerBound(ArrayList<Integer> list, int target) {
        int min = 0;
        int max = list.size();
        while(min < max) {
            int mid = (min + max) / 2;
            if(list.get(mid) >= target) {
                max = mid;
            }else {
                min = mid + 1;
            }
        }
        return max;
    }

    public static int upperBound(ArrayList<Integer> list, int target) {
        int min = 0;
        int max = list.size();
        while(min < max) {
            int mid = (min + max) / 2;
            if(list.get(mid) <= target) {
                min = mid + 1;
            }else {
                max = mid;
            }
        }
        return max;
    }

    public static void dfs(int dept, int end, int sum, ArrayList<Integer> list) {
        if (dept == end) {
            list.add(sum);
            return;
        }
        dfs(dept + 1, end, sum + arr[dept], list);
        dfs(dept + 1, end, sum, list);
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());
        arr = new int[N + 1];
        first = new ArrayList<>();
        second = new ArrayList<>();
        st = new StringTokenizer(br.readLine());
        for (int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        // 배열을 절반으로 나눠 각각 부분집합의 합의 리스트를 구한다.
        dfs(0, N / 2, 0, first);
        dfs(N / 2, N, 0, second);
        Collections.sort(first);
        Collections.sort(second);

        // 한 리스트에 대해 각 원소에 더했을 때 S를 완성할 수 있는 타겟의 수를 찾는다.
        long ans = 0;
        for(int i = 0; i < first.size(); i++) {
            int target = S - first.get(i);
            int upper = upperBound(second, target);
            int lower = lowerBound(second, target);
            ans += upper - lower;
        }
        System.out.println(S == 0 ? ans - 1 : ans);
    }
}

 

codeung.tistory.com/223 이 문제에서 N 제한이 두 배로 늘어난 문제.

 

같은 방법으로 풀었다가 시간 초과를 맞았다.

N의 상한은 40이므로 같은 방법으로 풀면 O(2^N) = 약 1조의 값으로 터져버린다.

 

구글링을 통해 정답을 확인했고 현재 수준으로는 절대 혼자서 해결할 수 없는 문제였다.

 

"중간에서 만나기"  (Meet in the middle) 방법을 사용해야 한다.

 

주어진 배열을 절반으로 나누어 각각 부분집합을 구한다.

(정확하게는 부분집합의 원소들을 모두 더한 값을 원소로 하는 리스트를 구한다.)

 

두 리스트에 대해서 서로의 원소의 합을 통해 S를 만들 수 있는 경우의 수를 구하면 된다.

이분탐색으로 target을 찾기 위해 먼저 정렬을 해놓아야 한다.

 

어차피 두 리스트 모두 처음 주어진 배열에 대한 부분집합이기 때문에 두 리스트의 원소를 더해서 S를 만들어도 상관 없다.

 

부분집합의 개수는 총 2^N개가 존재할 수 있고 이 경우 N의 상한이 40이므로 int 범위를 초과할 수 있기 때문에 long을 사용해야 한다.

 

 

 

이분 탐색을 사용한 upperBound(), lowerBound()는 그냥 외워버리자.

www.acmicpc.net/problem/1261

 

1261번: 알고스팟

첫째 줄에 미로의 크기를 나타내는 가로 크기 M, 세로 크기 N (1 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 미로의 상태를 나타내는 숫자 0과 1이 주어진다. 0은 빈 방을 의미하고, 1은 벽을 의미

www.acmicpc.net

문제

알고스팟 운영진이 모두 미로에 갇혔다. 미로는 N*M 크기이며, 총 1*1크기의 방으로 이루어져 있다. 미로는 빈 방 또는 벽으로 이루어져 있고, 빈 방은 자유롭게 다닐 수 있지만, 벽은 부수지 않으면 이동할 수 없다.

알고스팟 운영진은 여러명이지만, 항상 모두 같은 방에 있어야 한다. 즉, 여러 명이 다른 방에 있을 수는 없다. 어떤 방에서 이동할 수 있는 방은 상하좌우로 인접한 빈 방이다. 즉, 현재 운영진이 (x, y)에 있을 때, 이동할 수 있는 방은 (x+1, y), (x, y+1), (x-1, y), (x, y-1) 이다. 단, 미로의 밖으로 이동 할 수는 없다.

벽은 평소에는 이동할 수 없지만, 알고스팟의 무기 AOJ를 이용해 벽을 부수어 버릴 수 있다. 벽을 부수면, 빈 방과 동일한 방으로 변한다.

만약 이 문제가 알고스팟에 있다면, 운영진들은 궁극의 무기 sudo를 이용해 벽을 한 번에 다 없애버릴 수 있지만, 안타깝게도 이 문제는 Baekjoon Online Judge에 수록되어 있기 때문에, sudo를 사용할 수 없다.

현재 (1, 1)에 있는 알고스팟 운영진이 (N, M)으로 이동하려면 벽을 최소 몇 개 부수어야 하는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 미로의 크기를 나타내는 가로 크기 M, 세로 크기 N (1 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 미로의 상태를 나타내는 숫자 0과 1이 주어진다. 0은 빈 방을 의미하고, 1은 벽을 의미한다.

(1, 1)과 (N, M)은 항상 뚫려있다.

출력

첫째 줄에 알고스팟 운영진이 (N, M)으로 이동하기 위해 벽을 최소 몇 개 부수어야 하는지 출력한다.

예제 입력 1

3 3

011

111

110

예제 출력 1

3

예제 입력 2

4 2

0001

1000

예제 출력 2

0

예제 입력 3

6 6

001111

010000

001111

110001

011010

100010

예제 출력 3

2

 

 

 

 

 

 

 

 

풀이 . 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;

class Pair {
    int r, c;
    Pair(int r, int c) {
        this.r = r;
        this.c = c;
    }
}

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[][] map = null;
    static int[][] dist = null;
    static int N, M;

    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1 ,1};

    public static void bfs() {
        Queue<Pair> que = new ArrayDeque<>();
        Queue<Pair> nextQue = new ArrayDeque<>();
        que.add(new Pair(0, 0));
        dist[0][0] = 0;

        while(!que.isEmpty()) {
            Pair p = que.poll();

            for(int i = 0; i < 4; i++) {
                int nr = p.r + rArr[i];
                int nc = p.c + cArr[i];

                if(-1 < nr && nr < N && -1 < nc && nc < M) {
                    if(dist[nr][nc] == -1) {  // checking
                        if(map[nr][nc] == 0) {
                            dist[nr][nc] = dist[p.r][p.c];
                            que.add(new Pair(nr, nc));
                        }else {
                            dist[nr][nc] = dist[p.r][p.c] + 1;
                            nextQue.add(new Pair(nr, nc));
                        }
                    }
                }
            }

            if(que.isEmpty()) {
                que = nextQue;
                nextQue = new ArrayDeque<>();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        M = Integer.parseInt(st.nextToken());
        N = Integer.parseInt(st.nextToken());

        map = new int[N][M];
        dist = new int[N][M];
        for(int i = 0; i < N; i++) {
            String str = br.readLine();
            for(int j = 0; j < M; j++) {
                map[i][j] = str.charAt(j) - '0';
                dist[i][j] = -1;
            }
        }

        bfs();
        System.out.println(dist[N-1][M-1]);
    }
}

 

두 개의 큐를 사용한 BFS.

 

벽을 부수지 않고 이동할 수 있는 좌표들에 맞닿아있는 벽의 좌표들을 별도의 큐에 따로 담는다.

 

이 좌표들에 대해선 벽을 하나 부숴야 이동이 가능하므로 이전 dist에 +1한 값을 저장한다.

 

벽을 부수지 않고 이동 가능한 좌표들을 모두 사용했다면 큐를 바꿔 끼운 후 다음 벽들을 위한 새로운 큐를 만든다.

 

 

 

 

www.acmicpc.net/problem/1644

 

1644번: 소수의 연속합

첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 4,000,000)

www.acmicpc.net

문제

하나 이상의 연속된 소수의 합으로 나타낼 수 있는 자연수들이 있다. 몇 가지 자연수의 예를 들어 보면 다음과 같다.

  • 3 : 3 (한 가지)
  • 41 : 2+3+5+7+11+13 = 11+13+17 = 41 (세 가지)
  • 53 : 5+7+11+13+17 = 53 (두 가지)

하지만 연속된 소수의 합으로 나타낼 수 없는 자연수들도 있는데, 20이 그 예이다. 7+13을 계산하면 20이 되기는 하나 7과 13이 연속이 아니기에 적합한 표현이 아니다. 또한 한 소수는 반드시 한 번만 덧셈에 사용될 수 있기 때문에, 3+5+5+7과 같은 표현도 적합하지 않다.

자연수가 주어졌을 때, 이 자연수를 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 자연수 N이 주어진다. (1 ≤ N ≤ 4,000,000)

출력

첫째 줄에 자연수 N을 연속된 소수의 합으로 나타낼 수 있는 경우의 수를 출력한다.

예제 입력 1

20

예제 출력 1

0

예제 입력 2

3

예제 출력 2

1

예제 입력 3

41

예제 출력 3

3

예제 입력 4

53

예제 출력 4

2

 

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {
    static BufferedReader br = null;
    static int[] arr = null;
    static boolean[] check = null;
    static int N, idx, ans;

    public static void sieveOfEratosthenes() {
        for(int i = 2; i <= N; i++) {
            if(!check[i]) {
                arr[idx++] = i;
                for(int j = i*2; j <= N; j+=i) {
                    check[j] = true;
                }
            }
        }
    }

    public static void twoPointer() {
        int L = 0, R = 0;
        int sum = arr[0];

        while(R < idx) {
            if(sum < N) {
                R += 1;
                sum += arr[R];
            }else if(sum == N) {
                ans += 1;
                R += 1;
                sum += arr[R];
            }else if(sum > N) {
                sum -= arr[L];
                L += 1;
                if(L > R) {
                    R = L;
                    sum = arr[L];
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());
        arr = new int[N + 1];
        check = new boolean[N + 1];

        sieveOfEratosthenes();
        twoPointer();
        System.out.println(ans);
    }
}

 

투 포인터 + 에라토스테네스의 체.

 

N의 상한은 4백만이고 O(N * N^1/2)은 80억이므로 반드시 에라토스테네스의 체를 사용해 소수들의 집합을 구해야 한다.

 

 

 

 

www.acmicpc.net/problem/1806

 

1806번: 부분합

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

www.acmicpc.net

문제

10,000 이하의 자연수로 이루어진 길이 N짜리 수열이 주어진다. 이 수열에서 연속된 수들의 부분합 중에 그 합이 S 이상이 되는 것 중, 가장 짧은 것의 길이를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N (10 ≤ N < 100,000)과 S (0 < S ≤ 100,000,000)가 주어진다. 둘째 줄에는 수열이 주어진다. 수열의 각 원소는 공백으로 구분되어져 있으며, 10,000이하의 자연수이다.

출력

첫째 줄에 구하고자 하는 최소의 길이를 출력한다. 만일 그러한 합을 만드는 것이 불가능하다면 0을 출력하면 된다.

예제 입력 1

10 15

5 1 3 5 10 7 4 9 2 8

예제 출력 1

2

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[] arr = null;
    static int N, S, ans = 100001;

    public static void twoPointer() {
        int L = 0, R = 0;
        int sum = arr[0];

        while(R < N) {
            if(sum < S) {
                R += 1;
                sum += arr[R];
            }else if(sum >= S) {
                ans = Math.min(ans, R - L + 1);
                sum -= arr[L];
                L += 1;
                if(L > R) {  // 사실 입력 부분에서 길이 1이 정답인 경우를 미리 체크했으니 이 조건문은 빠져도 상관 없다.
                    R = L;
                    sum = arr[L];
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());

        st = new StringTokenizer(br.readLine());
        arr = new int[N + 1];  // R == N 됐을 때의 에러 방지
        for(int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
            if(arr[i] >= S) {
                System.out.println(1);
                return;
            }
        }

        twoPointer();
        System.out.println(ans == 100001 ? 0 : ans);
    }
}

 

2003번과 완전히 똑같은 투 포인터 문제.

www.acmicpc.net/problem/2003

 

2003번: 수들의 합 2

첫째 줄에 N(1 ≤ N ≤ 10,000), M(1 ≤ M ≤ 300,000,000)이 주어진다. 다음 줄에는 A[1], A[2], …, A[N]이 공백으로 분리되어 주어진다. 각각의 A[x]는 30,000을 넘지 않는 자연수이다.

www.acmicpc.net

문제

N개의 수로 된 수열 A[1], A[2], …, A[N] 이 있다. 이 수열의 i번째 수부터 j번째 수까지의 합 A[i] + A[i+1] + … + A[j-1] + A[j]가 M이 되는 경우의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 10,000), M(1 ≤ M ≤ 300,000,000)이 주어진다. 다음 줄에는 A[1], A[2], …, A[N]이 공백으로 분리되어 주어진다. 각각의 A[x]는 30,000을 넘지 않는 자연수이다.

출력

첫째 줄에 경우의 수를 출력한다.

예제 입력 1

4 2

1 1 1 1

예제 출력 1

3

예제 입력 2

10 5

1 2 3 4 2 5 3 1 1 2

예제 출력 2

3

 

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int[] arr = null;
    static int N, M, ans;

    public static void twoPointer() {
        int L = 0, R = 0;
        int sum = arr[0];

        while(R < N) {
            if(sum < M) {
                R += 1;
                sum += arr[R];
            }else if(sum == M) {
                ans += 1;
                R += 1;
                sum += arr[R];  // R == N이 될 때 인덱스 오류를 방지하기 위해 arr을 [N+1]로 잡고 [N]은 0으로 둔다.
            }else if(sum > M) {
                sum -= arr[L];
                L += 1;
                if(L > R) {  // L == R 인데 그 수가 M보다 크다면 L > R이 되어버림. 이에 대한 처리.
                    R = L;
                    sum = arr[L];
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

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

        twoPointer();
        System.out.println(ans);
    }
}

 

투 포인터 문제.

 

문제를 읽자마자 반사적으로 O(N^2)의 방법이 떠오른다.

N^2 = 1억으로 1초 안에 해결이 가능하지만 문제의 제한시간은 0.5초.

 

투 포인터를 사용한 O(N)으로 해결해야 한다.

 

 

 

 

www.acmicpc.net/problem/1182

 

1182번: 부분수열의 합

첫째 줄에 정수의 개수를 나타내는 N과 정수 S가 주어진다. (1 ≤ N ≤ 20, |S| ≤ 1,000,000) 둘째 줄에 N개의 정수가 빈 칸을 사이에 두고 주어진다. 주어지는 정수의 절댓값은 100,000을 넘지 않는다.

www.acmicpc.net

문제

N개의 정수로 이루어진 수열이 있을 때, 크기가 양수인 부분수열 중에서 그 수열의 원소를 다 더한 값이 S가 되는 경우의 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 정수의 개수를 나타내는 N과 정수 S가 주어진다. (1 ≤ N ≤ 20, |S| ≤ 1,000,000) 둘째 줄에 N개의 정수가 빈 칸을 사이에 두고 주어진다. 주어지는 정수의 절댓값은 100,000을 넘지 않는다.

출력

첫째 줄에 합이 S가 되는 부분수열의 개수를 출력한다.

예제 입력 1

5 0

-7 -3 -2 5 8

예제 출력 1

1

 

 

 

 

 

 

 

풀이 1. (틀린 코드)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int N, S, ans;
    static int[] arr = null;
    static boolean[] check = null;

    public static void dfs(int last, int sum) {
        if(sum == S) {
            ans += 1;
        }
        for(int i = 0; i < N; i++) {
            if(!check[i] && arr[i] >= last) {
                check[i] = true;
                dfs(arr[i], sum + arr[i]);
                check[i] = false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());

        arr = new int[N];
        check = new boolean[N];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        for(int i = 0; i < N; i++) {
            check[i] = true;
            dfs(arr[i], arr[i]);
            check[i] = false;
        }
        System.out.println(ans);
    }
}

 

33%에서 시간 초과.

 

지금까지의 브루트 포스 문제들과는 조금 다르다.

 

지금까지의 문제들은 거의 두 경우로 나뉘어졌다.

1.  2차원 배열을 탐색하는 방식.

2. 특정 dept를 목표로하여 탐색하는 방식.

 

그런데 이 문제는 목표로 하는 숫자의 합을 찾아야 한다.

음수를 더하는 경우도 있기 떄문에 if(sum == S) 에 들어와도 ans += 1을 할 뿐 return 하지는 않고 그대로 탐색을 이어나가야 한다.

 

즉, N의 최대값이 20이므로  20까지 dept를 갖는 DFS를 다 수행해야 한다는 것.

목표 dept를 1로 하여 DFS,

목표 dept를 2로 하여 DFS,

목표 dept를 3으로 하여 DFS,

.
.

.

목표 dept를 20으로 하여 DFS,

 

그렇기 때문에 시간초과가 발생하는 듯 하다.

 

 

 

 

 

 

풀이 2. (정답 코드)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static int N, S, ans;
    static int[] arr = null;

    public static void dfs(int dept, int sum) {
        if(dept == N) {
            if(sum == S) {
                ans += 1;
            }
            return;
        }
        dfs(dept + 1, sum + arr[dept]);  // 해당 인덱스를 포함
        dfs(dept + 1, sum);  // 해당 인덱스를 포함하지 않음
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());

        arr = new int[N];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i < N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        dfs(0, 0);
        if(S == 0) {  // S == 0일 경우 공집합도 체크하게 됨. 공집합에 대한 정답 1 제거.
            ans -= 1;
        }
        System.out.println(ans);
    }
}

 

목표 dept를 N으로 두고 배열의 끝까지 탐색을 실시한다.

탐색 과정에서 해당 원소를 포함할 것인지 포함하지 않을 것인지에 대한 경우로 재귀를 실시한다.

 

 

 

 

근데 이것도 결국 모든 경우를 따지게 되는 거 아닌가..?

아.. 모르겠다...

 

->

 

순서를 중요시하는 조합이 아니라 어떤 수가 포함되느냐 아니냐만 따지면 되느냐가 중요하다는 게 차이인 듯.

 

하나의 원소에 대해서 그 수를 포함하느냐 포함하지 않느냐 두 가지 경우가 나오기 때문에 O(2^N)으로 처리가 가능하다.

(2 ^ 20 = 약 1백만)

 

 

 

 

'알고리즘 문제 > 백준 온라인 저지' 카테고리의 다른 글

[BOJ] 1806 - 부분합 JAVA  (0) 2021.02.16
[BOJ] 2003 - 수들의 합 2 JAVA  (0) 2021.02.15
[BOJ] 6603 - 로또 JAVA  (0) 2021.02.15
[BOJ] 1987 - 알파벳 JAVA  (0) 2021.02.15
[BOJ] 2580 - 스도쿠 JAVA  (0) 2021.02.15

www.acmicpc.net/problem/6603

 

6603번: 로또

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있다. 첫 번째 수는 k (6 < k < 13)이고, 다음 k개 수는 집합 S에 포함되는 수이다. S의 원소는 오름차순으로

www.acmicpc.net

문제

독일 로또는 {1, 2, ..., 49}에서 수 6개를 고른다.

로또 번호를 선택하는데 사용되는 가장 유명한 전략은 49가지 수 중 k(k>6)개의 수를 골라 집합 S를 만든 다음 그 수만 가지고 번호를 선택하는 것이다.

예를 들어, k=8, S={1,2,3,5,8,13,21,34}인 경우 이 집합 S에서 수를 고를 수 있는 경우의 수는 총 28가지이다. ([1,2,3,5,8,13], [1,2,3,5,8,21], [1,2,3,5,8,34], [1,2,3,5,13,21], ..., [3,5,8,13,21,34])

집합 S와 k가 주어졌을 때, 수를 고르는 모든 방법을 구하는 프로그램을 작성하시오.

입력

입력은 여러 개의 테스트 케이스로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있다. 첫 번째 수는 k (6 < k < 13)이고, 다음 k개 수는 집합 S에 포함되는 수이다. S의 원소는 오름차순으로 주어진다.

입력의 마지막 줄에는 0이 하나 주어진다. 

출력

각 테스트 케이스마다 수를 고르는 모든 방법을 출력한다. 이때, 사전 순으로 출력한다.

각 테스트 케이스 사이에는 빈 줄을 하나 출력한다.

예제 입력 1

7 1 2 3 4 5 6 7

8 1 2 3 5 8 13 21 34 0

예제 출력 1

1 2 3 4 5 6

1 2 3 4 5 7

1 2 3 4 6 7

1 2 3 5 6 7

1 2 4 5 6 7

1 3 4 5 6 7

2 3 4 5 6 7

 

1 2 3 5 8 13

1 2 3 5 8 21

1 2 3 5 8 34

1 2 3 5 13 21

1 2 3 5 13 34

1 2 3 5 21 34

1 2 3 8 13 21

1 2 3 8 13 34

1 2 3 8 21 34

1 2 3 13 21 34

1 2 5 8 13 21

1 2 5 8 13 34

1 2 5 8 21 34

1 2 5 13 21 34

1 2 8 13 21 34

1 3 5 8 13 21

1 3 5 8 13 34

1 3 5 8 21 34

1 3 5 13 21 34

1 3 8 13 21 34

1 5 8 13 21 34

2 3 5 8 13 21

2 3 5 8 13 34

2 3 5 8 21 34

2 3 5 13 21 34

2 3 8 13 21 34

2 5 8 13 21 34

3 5 8 13 21 34

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static StringBuilder sb = null;

    static int K;
    static int[] S = null;
    static int[] lotto = null;
    static boolean[] check = null;

    public static void dfs(int dept) {
        if(dept == 6) {
            for(int i = 0; i < 6; i++) {
                sb.append(lotto[i] + " ");
            }
            sb.append("\n");
            return;
        }

        for(int i = 0; i < K; i++) {
            if(!check[i] && S[i] > lotto[dept - 1]) {
                lotto[dept] = S[i];
                check[i] = true;
                dfs(dept + 1);
                check[i] = false;
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        sb = new StringBuilder();
        while(true) {
            st = new StringTokenizer(br.readLine());
            K = Integer.parseInt(st.nextToken());

            if(K == 0) break;

            S = new int[K];
            lotto = new int[6];
            check = new boolean[K];
            for(int i = 0; i < K; i++) {
                S[i] = Integer.parseInt(st.nextToken());
            }

            for(int i = 0; i < K; i++) {
                lotto[0] = S[i];
                check[i] = true;
                dfs(1);
                check[i] = false;
            }
            sb.append("\n");
        }
        System.out.println(sb.toString());
    }
}

 

백트래킹을 통해 가능한 모든 조합을 만들어 낸다.

 

주의할 점은, 오름차순 조합에 대해서만 출력하는 것이다.

즉, 같은 숫자들로 만들어진 조합이라도 오름차순일 때만 정답으로 친다.

ex ) 1, 2, 3, 4, 5, 6 으로 만들어진 여러 조합들 (1, 2, 3, 4, 5, 6), ... , (6, 5, 4, 3, 2, 1) 중에서는 (1, 2, 3, 4, 5, 6) 하나만 정답으로 친다는 것.

 

이를 위해 현재 lotto[]의 마지막 원소보다 값이 큰 경우에만 재귀를 하도록 하는 조건을 추가했다.

 

이렇게하면 오름차순이 유지되지 않는 조합은 만드는 것을 중단하기 때문에 모든 조합을 만들어놓고 오름차순 여부를 검사하는 것보다 더 빠르게 실행할 수 있다.

 

사실 이것보다 더 효율적인 방법이 있긴 하다.

 

사전에 S[]를 미리 오름차순 정렬해놓고,

이전에 입력받은 숫자의 S[] 에서의 인덱스를 기억하며 그 인덱스보다 큰 인덱스를 갖는 S[]의 원소만 재귀하도록 하는 것이다.

    public static void dfs(int idx, int dept) {
        if(dept == 6) {
            for(int i = 0; i < 6; i++) {
                sb.append(lotto[i] + " ");
            }
            sb.append("\n");
            return;
        }

        for(int i = idx; i < K; i++) {
            if(!check[i]) {
                lotto[dept] = S[i];
                check[i] = true;
                dfs(i + 1, dept + 1);
                check[i] = false;
            }
        }
    }

 

이렇게 하면 헛도는 반복마저도 하지 않게 될 수 있으니 더 빨리 처리할 수 있게 된다.

(사실 그렇게 큰 차이는 아닌 듯 하다.)

 

 

그렇게 큰 차이도 아니고 코드를 이해하기가 더 쉬운 위유로 첫번째 방법이 더 나은 듯 하다.

 

 

 

 

www.acmicpc.net/problem/1987

 

1987번: 알파벳

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다. 말은 상하좌우로 인접한 네 칸 중의 한 칸으

www.acmicpc.net

문제

세로 R칸, 가로 C칸으로 된 표 모양의 보드가 있다. 보드의 각 칸에는 대문자 알파벳이 하나씩 적혀 있고, 좌측 상단 칸 (1행 1열) 에는 말이 놓여 있다.

말은 상하좌우로 인접한 네 칸 중의 한 칸으로 이동할 수 있는데, 새로 이동한 칸에 적혀 있는 알파벳은 지금까지 지나온 모든 칸에 적혀 있는 알파벳과는 달라야 한다. 즉, 같은 알파벳이 적힌 칸을 두 번 지날 수 없다.

좌측 상단에서 시작해서, 말이 최대한 몇 칸을 지날 수 있는지를 구하는 프로그램을 작성하시오. 말이 지나는 칸은 좌측 상단의 칸도 포함된다.

입력

첫째 줄에 R과 C가 빈칸을 사이에 두고 주어진다. (1 ≤ R,C ≤ 20) 둘째 줄부터 R개의 줄에 걸쳐서 보드에 적혀 있는 C개의 대문자 알파벳들이 빈칸 없이 주어진다.

출력

첫째 줄에 말이 지날 수 있는 최대의 칸 수를 출력한다.

예제 입력 1

2 4

CAAB

ADCB

예제 출력 1

3

 

 

 

 

 

 

풀이 .

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
    static BufferedReader br = null;
    static StringTokenizer st = null;
    static char[][] map = null;
    static boolean[] check = null;
    static int[] rArr = {-1, 1, 0, 0};
    static int[] cArr = {0, 0, -1, 1};
    static int R, C, ans;

    public static void dfs(int r, int c, int dept) {
        ans = Math.max(ans, dept);

        for(int i = 0; i < 4; i++) {
            int nr = r + rArr[i];
            int nc = c + cArr[i];

            if(-1 < nr && nr < R && -1 < nc && nc < C) {
                char ch = map[nr][nc];
                if(!check[ch - 65]) {
                    check[ch - 65] = true;
                    dfs(nr, nc, dept + 1);
                    check[ch - 65] = false;
                }
            }
        }
    }

    public static void main(String[] args) throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        st = new StringTokenizer(br.readLine());
        R = Integer.parseInt(st.nextToken());
        C = Integer.parseInt(st.nextToken());

        map = new char[R][C];
        check = new boolean[26];
        for(int i = 0; i < R; i++) {
            String str = br.readLine();
            for(int j = 0; j < C; j++) {
                map[i][j] = str.charAt(j);
            }
        }

        char ch = map[0][0];
        check[ch - 65] = true;
        dfs(0, 0, 1);
        System.out.println(ans);
    }
}

 

백트래킹을 이용한 브루트 포스.

+ Recent posts