개발일지/알고리즘

백준 1182번 : 부분수열의 합

김진우 개발일지 2025. 2. 2. 18:07

Meet in the Middle 개념

Meet in the Middle은 주어진 문제를 두 개의 부분 문제로 나누어 해결하는 방식입니다.
완전 탐색을 할 경우, 모든 부분수열을 탐색하면 시간 복잡도가 O(2^N)이지만, Meet in the Middle을 사용하면 두 부분으로 나누어 O(2^(N/2))로 줄일 수 있습니다.

이 방법을 이용하면 시간 복잡도를 획기적으로 줄일 수 있어 N=40 정도까지도 충분히 해결할 수 있습니다.

 

Meet in the Middle을 3개 그룹으로 나누는 경우

보통 2개 그룹으로 나누는 이유는:

  • 모든 부분집합을 구하는 데 O(2^N/2) 로 줄일 수 있기 때문입니다.
  • 그 후, unordered_map을 이용해 빠르게 대응되는 합을 찾을 수 있기 때문입니다.

하지만,
3개 그룹으로 나누면 O(2^(N/3)) 로 더 줄일 수 있음
✅ 다만, 탐색 시 2개의 그룹만 조회하던 방식이 3개 그룹을 조회해야 하므로 구현이 복잡해짐

 

시간 복잡도 비교

  • 2개 그룹으로 나눌 경우
    → 각 그룹에서 O(2^(N/2)) 만큼 부분집합 합을 저장하고,
    탐색이 O(2^(N/2)) 이므로 O(2^(N/2))
  • 3개 그룹으로 나눌 경우
    → 각 그룹에서 O(2^(N/3)) 만큼 부분집합 합을 저장하고,
    두 개 그룹의 모든 합을 검사해야 하므로 O(2^(2N/3))
    → 따라서, O(2^(2N/3)) 가 됨.

즉, 3개로 나누면 개별 그룹의 크기는 줄어들지만, 탐색 비용이 커져서 효과가 애매할 수 있음
✅ 보통 NN이 40 이상으로 클 때, 2개로 나누는 것이 더 효율적이고, 3개로 나누는 것은 특수한 경우에만 유용함.

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

void subsetSum(int start, int end, vector<int>& a, unordered_map<int, int>& m) {
    int n = end - start;
    for (int i = 0; i < (1 << n); i++) {
        int sum = 0;
        for (int j = 0; j < n; j++)
            if (i & (1 << j)) sum += a[start + j];
        m[sum]++;
    }
}

int main() {
    int n, s;
    cin >> n >> s;
    vector<int> a(n);
    for (int i = 0; i < n; i++) cin >> a[i];

    unordered_map<int, int> left, right;
    subsetSum(0, n / 2, a, left);
    subsetSum(n / 2, n, a, right);

    long long cnt = 0;
    for (auto& [sum, freq] : left) {
        if (right.count(s - sum)) cnt += (long long)freq * right[s - sum];
    }
    if (s == 0) cnt--;
    cout << cnt;
}