핵심::
MEET IN THE MIDDLE 개념 문제

풀이::
MEET IN THE MIDDLE 알고리즘은 1보다 큰 N에 대해 N^M 보다 N^(M/K) * K 가 더 작다는 아이디어로 만들어진 분할 정복의 일종이다.

숫자가 N개 주어질 때 모든 합의 가능 여부를 체크하려면 경우의 수가 2^N가지 있다. 

2^40은 1,099,511,627,776 이므로 주어진 40개의 숫자를 전수 검사하는 방식으로는 시간 안에 해결 할 수 없다.

20개씩 나눠 첫 세트에 대해 전수 검사를 진행하고 S - 숫자 의 값이 두번째 세트에서 가능하다면 탐색을 종료한다.

탐색하는 S를 만드는 조합이 불가능한 경우가 최악의 경우로 이 때 탐색하는 횟수는 (2^20) * 2 = 2,097,152 이다.

의견::
분할 정복은 쪼개진 문제들을 (N/K)^M의 복잡도로 풀고 나눠진 K개의 문제들을 다시 합치는 과정이 있다는 차이점이 있다고 하는데

MEET IN THE MIDDLE 도 결국 계산한 K개의 문제들을 다시 합치는 과정이 있는데 뭐가 다르다는 건지는 잘 모르겠다. 

쪼갠 순서대로 합치는 부분이 다른건가?

 

코드::

더보기
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;
using ull = long long;

ull s1[8000001];

int main()
{
	int n, k;
	cin >> n >> k;
	vector<int> arr(n + 1);
	k += 4000000;
	int t = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", &arr[i]);
		t += arr[i];
	}

	int n1 = n / 2;
	for (int i = 0; i < pow(2, n1); i++) {
		int j = i;
		int sum = 4000000;
		int cnt = 0;
		while (j > 0) {
			cnt++;
			if (j % 2 == 1) {
				sum += arr[cnt];
			}			
			j /= 2;
		}		
		s1[sum]++;
	}
		
	ull dap = 0;
	
	for (int i = 0; i < pow(2, n - n1); i++) {
		int j = i;
		int sum = 0;
		int cnt = n1;
		while (j > 0) {
			cnt++;
			if (j % 2 == 1) {
				sum += arr[cnt];
			}
			j /= 2;
		}
		if (k - sum < 0) continue;
		dap += s1[k - sum];
	}
	if (k == 4000000) dap -= 1;
	cout << dap;
	return 0;
}