알고리즘 문제) BOJ 1450. 냅색문제
문제 요약
- N개의 물건이 존재하며,
- 최대 C만큼 넣을 수 있는 가방 존재
- 이때, N개의 물건을 가방에 넣는 방법의 수는?
시간 제한
- 1초
입력
- N C
- N : 1 ~ 30
- C : 0 ~ 10억
- 물건의 무게가 차례대로 한줄에 주어짐
- 무게 : 1 ~ 10억
출력
- 가방에 넣는 방법의 수는?
접근법
- 완전탐색으로 먼저 접근하기
- 가방은 선택O or 선택 X 두 케이스로 나뉜다.
- 백트랙킹을 통해, 가리키는 가방을 선택하는 경우, 선택하지 않는 경우로 나눠서 재귀 호출 ⇒ 이때, 무게 합이 C보다 큰 경우는 가지치기
- 문제점 : 완탐으로 할 경우 최악의 경우 케이스가 2의 30승까지 가능하므로 시간초과
- 그렇다고 dp로 접근한다면 메모이제이션 배열에 현재 가리키는 배낭 인덱스, 현재까지 배낭 무게 합이 필요하므로 10억10억4byte로 메모리 초과
- 최적화 진행 ⇒ 중간에서 만나기
- N개의 배낭을 두 개의 그룹으로 나눈 뒤
- 각각의 그룹에서 나올 수 있는 경우의 합을 리스트에 담아둔다.
⇒ 2*2^(N/2) - 이후 두 리스트를 각각 정렬 후에, 이분탐색으로 두 원소값을 합했을 때 10이하인 경우의 수를 구한다.
- 즉, A리스트에서 원소값이 1이라면, B리스트에서 C-1이하인 원소 중 가장 큰 원소의 인덱스+1값 구하기
⇒ 2*logN
- 즉, A리스트에서 원소값이 1이라면, B리스트에서 C-1이하인 원소 중 가장 큰 원소의 인덱스+1값 구하기
- 최종 시간 복잡도 : 2^(N/2 + 1) + (N/2)*log(N/2) ⇒ 2^16
코드
import java.io.*;
import java.util.*;
public class Main {
static int N, C;
static int[] arr;
static ArrayList<Integer> aLst = new ArrayList<>();
static ArrayList<Integer> bLst = new ArrayList<>();
static void separate(int cur, int sum, int end) {
if (sum > C) return;
if (cur == end) {
if (end == N / 2) aLst.add(sum);
else bLst.add(sum);
return;
}
// 현재 가방 선택
separate(cur + 1, sum + arr[cur], end);
// 현재 가방 선택 X
separate(cur + 1, sum, end);
}
static int binarySearch(int aNum) {
int s = 0, e = bLst.size() - 1, mid, ans = 0;
while (s <= e) {
mid = (s + e) / 2;
if (aNum + bLst.get(mid) > C) {
e = mid - 1;
} else {
ans = mid + 1;
s = mid + 1;
}
}
return ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
C = 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());
}
// a리스트 만들기
separate(0, 0, N / 2);
// b리스트 만들기
separate(N / 2, 0, N);
Collections.sort(bLst);
int num, cnt = 0;
for (int i = 0, size = aLst.size(); i < size; i++) {
num = aLst.get(i);
cnt += binarySearch(num);
}
System.out.println(cnt);
}
}
알고리즘 문제) BOJ 1280. 부분수열의 합 2
문제 요약
- N개의 정수로 이루어진 수열이 있을 때, 크기가 양수인 부분수열 중 수열의 원소를 다 더한 값이 S가 되는 경우의 수는?
시간 제한
- 1초
입력
- 정수의 개수 N과, 정수 S
- 1 ≤ N ≤ 40
- -100만 ≤ S ≤ 100만
- N개의 정수가 빈칸을 두고 주어짐
- 정수의 절댓값은 10만 이하
출력
- 합이 S가 되는 부분수열의 개수를 출력
접근법
- 완탐으로 접근한다면
- 각 숫자를 선택 O 또는 선택 X 2가지 케이스가 존재하므로 최악의 경우 2^40가지가 존재 ⇒ 시간초과
- 이분탐색 + 중간에서 만나기 알고리즘으로 최적화 진행
- 각각 N/2개의 원소를 갖는 두 그룹으로 나눈 뒤
- 각 그룹에서 나올 수 있는 모든 경우의수 찾기 ⇒ 2 * 2^(N/2) = 2백만..
- 이때, 나올 수 있는 경우의 수는 각각 리스트(a,b)에 담아둔다.
- 이후, a리스트 기준으로 원소 하나씩 꺼내면서 (for문)
- 정렬된 b리스트에서 이분탐색으로 합이 S가 되는 수중 가장 큰 인덱스(maxIdx)와 가장 작은 인덱스(minIdx)를 구한 뒤, 차이값(maxIdx-minIdx) + 1로 개수를 구하기
- 해당 값을 누적해서 더한 값이 정답이 된다.
⇒ (N/2) * log(N/2)
- 해당 값을 누적해서 더한 값이 정답이 된다.
- 정렬된 b리스트에서 이분탐색으로 합이 S가 되는 수중 가장 큰 인덱스(maxIdx)와 가장 작은 인덱스(minIdx)를 구한 뒤, 차이값(maxIdx-minIdx) + 1로 개수를 구하기
- 따라서 N이 40까지 가능하므로 최악의 경우 2^21개의 연산만 수행하게 된다.
- 추가로 생각해야할 점(엣지 케이스)
- S가 0인 경우와 0이 아닌 경우를 구분해야한다.
- S가 0인 경우, 아무것도 선택하지 않은 경우에도 카운트 될 수 있으므로 최종 합계에서 1을 빼준 값이 정답이다.
- 나올 수 있는 경우의 수는 int 범위를 벗어날 수 있다.
- A그룹에서 2^20, B그룹에서 2^20가지가 존재할 수 있으며,
- 만약 모든 경우가 가능한 경우라면 2^20*2^20 = 2^40가지가 정답이 됨
- A그룹에서 2^20, B그룹에서 2^20가지가 존재할 수 있으며,
- S가 0인 경우와 0이 아닌 경우를 구분해야한다.
코드
import java.io.*;
import java.util.*;
public class Main {
static int N, S;
static int[] arr;
static ArrayList<Integer> aLst = new ArrayList<>();
static ArrayList<Integer> bLst = new ArrayList<>();
static void separate(int cur, int end, int sum) {
if (cur == end) {
if (end == N / 2) aLst.add(sum);
else bLst.add(sum);
return;
}
// 현재 숫자 선택 O
separate(cur + 1, end, sum + arr[cur]);
// 현재 숫자 선택 X
separate(cur + 1, end, sum);
}
// 합이 S가 되는 가장 작은 인덱스 출력
static int getMinIdx(int aNum) {
int s = 0, e = bLst.size() - 1, mid, ans = -1;
long sum;
while (s <= e) {
mid = (s + e) / 2;
sum = aNum + bLst.get(mid);
if (sum == S) {
// 정답 변수에 담기
ans = mid;
// 최대한 작은 인덱스를 찾는 것이므로
e = mid - 1;
} else if (sum > S) {
// 합을 줄여야 하므로
e = mid - 1;
} else {
// 합을 늘려야 하므로
s = mid + 1;
}
}
return ans;
}
// 합이 S가 되는 가장 큰 인덱스 출력
static int getMaxIdx(int aNum) {
int s = 0, e = bLst.size() - 1, mid, ans = -1;
long sum;
while (s <= e) {
mid = (s + e) / 2;
sum = aNum + bLst.get(mid);
if (sum == S) {
// 정답 변수에 담기
ans = mid;
// 최대한 큰 인덱스를 찾는 것이므로
s = mid + 1;
} else if (sum > S) {
// 합을 줄여야 하므로
e = mid - 1;
} else {
// 합을 늘려야 하므로
s = mid + 1;
}
}
return ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
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());
}
separate(0, N / 2, 0);
separate(N / 2, N, 0);
Collections.sort(bLst);
int num, max, min;
long cnt = 0;
for (int i = 0, size = aLst.size(); i < size; i++) {
num = aLst.get(i);
max = getMaxIdx(num);
min = getMinIdx(num);
if (max != -1 && min != -1) {
cnt += (max - min + 1);
}
}
// S가 0인경우 부분 수열 크기가 0인 경우는 제거해야하므로 -1
if (S == 0) System.out.println(cnt - 1);
else System.out.println(cnt);
}
}
'📙 Algorithm Solving > Java' 카테고리의 다른 글
📙[Algo] 24.08.01 알고리즘 (0) | 2024.08.01 |
---|---|
📙[Algo] 24.07.30 알고리즘 (0) | 2024.07.30 |
📙[Algo] 24.07.18 알고리즘 (0) | 2024.07.18 |
📙[Algo] 24.07.16 알고리즘 (0) | 2024.07.17 |
📙[Algo] 24.07.13~24.07.15 알고리즘 (1) | 2024.07.15 |