[백준] 1208 부분수열의 합 2
부분수열의 합 1과 다 똑같은데 N범위가 두배로 늘어났다. 따라서 1에서 처럼 모든 경우의 수를 다 찾는(2^N)것은 1조가 넘어가서 힘들다.
그래서 경우를 두개로 쪼개서 풀어준다. 주어진 수열을 반으로 쪼개면 최대 20개씩 쪼개지므로 1에서 처럼 부분 수열의 합을 전수조사해서 클리어할 수 있다. 다만 중간중간 주의해야할 점들이 있다. 자세한것은 주석으로 보자~!
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.StringTokenizer;
public class Main {
static int ip(String s) { return Integer.parseInt(s); };
static HashSet<Long> set1; // 첫번째 부분수열을 중복없이 저장할 해쉬셋
static HashSet<Long> set2; // 두번째 부분수열을 중복없이 저장할 해쉬셋
static HashMap<Long, Integer> map1; // 키 : 부분수열의 합 , 값 : 그 합이 나오는 경우의 수
static HashMap<Long, Integer> map2;
// 부분수열의 합을 구할 함수
// 인자는 순서대로 해당수열 내 인덱스, 여태까지 모은 합, 해당수열의 크기, 해당 수열, 수열의 이름(1 혹은 2), 여태까지 포함한 원소의 갯수
public static void go(int idx, long sum, int size, int[] arr, int name, int cnt){
if(idx >= size){ // 모든 원소에 대해 넣기 or 안넣기 경우가 모두 끝났다면
if(cnt > 0) { // 아무것도 안고르는건 안된다(부분수열의 크기가 양수라고 명시되어있음)
if(name == 1) { // 1번 배열이라면
if(map1.containsKey(sum)) { // 기존에 해쉬맵에 들어있었다면
map1.put(sum, map1.get(sum)+1); // 갯수 +1
}else { // 처음 들어오는거라면
set1.add(sum); // set에도 넣고
map1.put(sum, 1); // map에도 새로 추가
}
}else {
set2.add(sum);
if(map2.containsKey(sum)) {
map2.put(sum, map2.get(sum)+1);
}else {
map2.put(sum, 1);
}
}
}
return;
}
go(idx+1, sum + arr[idx], size, arr, name, cnt+1); // 해당 인덱스의 수열을 포함해서 진행
go(idx+1, sum, size, arr, name, cnt); // 안포함하고 진행
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = ip(st.nextToken());
int s = ip(st.nextToken());
int[] arr = new int[n]; // 전체 수열을 받는다
st = new StringTokenizer(br.readLine());
for(int i = 0 ; i < n ; i++) {
arr[i] = ip(st.nextToken());
}
// 크기를 절반짤라서 두개의 작은 수열로 나눈다
int[] sub1 = new int[n/2];
System.arraycopy(arr, 0, sub1, 0, n/2);
int[] sub2 = new int[n-n/2];
System.arraycopy(arr, n/2, sub2, 0, n-n/2);
set1 = new HashSet<Long>();
set2 = new HashSet<Long>();
map1 = new HashMap<Long, Integer>();
map2 = new HashMap<Long, Integer>();
// 첫번째 작은 수열부터 출발~
go(0, 0, n/2, sub1, 1, 0);
// 두번째 작은 수열 출발
go(0, 0, n-n/2, sub2, 2, 0);
// 크기순으로 정렬해줘야 하므로 set은 더이상 사용할 수 없다
ArrayList<Long> sum1 = new ArrayList<Long>(set1);
ArrayList<Long> sum2 = new ArrayList<Long>(set2);
// 정렬
Collections.sort(sum1);
Collections.sort(sum2);
// 정답을 저장할 변수
long ans = 0;
// 일단 쪼갠 수열 자체적으로 s를 만족시킬 수도 있지 않나?
// 있다면 정답에 더해주도록 하자
if(map1.containsKey((long)s)){
ans += map1.get((long)s);
}
if(map2.containsKey((long)s)){
ans += map2.get((long)s);
}
// 이제 본격적으로 두 작은수열의 조합을 통해 s를 만들 수 있는지 살펴보도록 하자
// sub1 은 0번째 인덱스부터 시작하고, sub2는 가장 큰 인덱스부터 시작한다
int l = 0;
int r = sum2.size()-1;
while(l < sum1.size() && r >= 0) {
long add = sum1.get(l) + sum2.get(r); // 두 값의 합
if(add == s) { // 이 우리가 구할 S와 같다면
// 두 값을 만들어낼 수 있는 경우의 수 끼리 곱해서 ans에 더해준다
// 나는 해쉬맵의 값 부분을 integer로 정의했기 때문에 앞에 형변환을 안붙혀주면 최악의 경우 터질 수 있었다
ans += (long)map1.get(sum1.get(l)) * (long)map2.get(sum2.get(r));
// 진행
l++;
r--;
}else if( add < s ) {
l++;
}else {
r--;
}
}
System.out.println(ans);
}
}
Comments