[BOJ] 1208. 부분수열의 합 2 - (Java)

Problem

제출일 : 2020-03-16

문제 풀이 시간 : 5H

난이도 : ★★★★★


link : https://www.acmicpc.net/problem/1208

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

Input

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

Output

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

Example

input

5 0
-7 -3 -2 5 8

output

1

Solution & Inpression

1182. 부분수열의 합문제에서 N이 40까지 커진문제...

여러 블로그를 보며 문제에 해답을 얻고 문제를 해결했다.


문제를 해결한 아이디어는 주어진 입력을 절반씩 나누어 A, B로 구분을 했을때 A와 B는 최대 20개의 숫자를 갖는다. A와 B의 모든 부분수열의 합을 List에 저장을 하였다. (N=20은 주어진 시간내에 완전 탐색이 가능하다.)

부분 수열의 합이 S가 되는 경우는

  1. A List의 값이 S 인 경우
  2. B List의 값이 S 인 경우
  3. A List의 값이 x라 할 때 B List 값이 S-x 인 경우

로 총 3가지의 경우 합이다.

C에 algorithm 라이브러리에 포함된 함수인 lower_bound와 upper_bound를 구현해 문제를 해결했다.

따로 위 함수들은 정리하겠다.

long ans: 모든 경우의 수는 $2^{40}$으로 int형을 사용하면 범위가 초과하니 주의 해야한다.

Code

언어 : JAVA

메모리 : 83140 kb

실행 시간 : 1360 ms

import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

public class Main {
    static int N, S;
    static int ans; // int :2^32 long: 2^64 >> 문제: 2^40
    static int[] input;
    static ArrayList<Integer> L, R;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
        S = sc.nextInt();
        input = new int[N];
        for (int i = 0; i < N; i++) {
            input[i] = sc.nextInt();
        }

        L = new ArrayList<>();
        R = new ArrayList<>();
        solve(0, N / 2, 0, L);
        solve(N / 2, N, 0, R);

        Collections.sort(R);

        ans = 0;
        for (int val : L) {
            val = S - val;
            int hi = upper_bound(R, val);
            int lo = lower_bound(R, val);
            ans += hi - lo;
        }

        // 공집합 제거
        if (S == 0) {
            --ans;
        }
        System.out.println(ans);
    }

    // lower bound는 찾고자 하는 값 이상이 처음 나타나는 위치
    private static int lower_bound(ArrayList<Integer> list, int val) {
        int start = 0;
        int end = list.size();
        int mid;
        while (start < end) {
            mid = (start + end) >> 1;
            if (list.get(mid) >= val) {
                end = mid;
            } else {
                start = mid + 1;
            }
        }
        return start;
    }

    // upper bound는 찾고자 하는 값보다 큰 값이 처음으로 나타나는 위치
    private static int upper_bound(ArrayList<Integer> list, int val) {
        int start = 0;
        int end = list.size();
        int mid;
        while (start < end) {
            mid = (start + end) >> 1;
            if (list.get(mid) <= val) {
                start = mid + 1;
            } else {
                end = mid;
            }
        }
        return start;
    }

    // 범위에 해당하는 모든 부분수열의 합을 저장.
    private static void solve(int pos, int end, int sum, ArrayList<Integer> list) {
        if (pos == end) {
            list.add(sum);
            return;
        }
        solve(pos + 1, end, sum, list);
        solve(pos + 1, end, sum + input[pos], list);
    }

}

Reference

http://joonas-yoon.blogspot.com/2016/03/1208-2.html

https://23dotory.tistory.com/27

'Problem > BOJ' 카테고리의 다른 글

[BOJ] 7453. 합이 0인 네 정수 - (Java)  (0) 2020.03.17
[BOJ] 2143. 두 배열의 합 - (Java)  (0) 2020.03.17
[BOJ] 1806. 부분합 - (Java)  (0) 2020.03.15
[BOJ] 2003. 수들의 합 2 - (Java)  (2) 2020.03.15
[BOJ] 12100. 2048 (Easy) - (Java)  (0) 2020.03.15