https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV7IzvG6EksDFAXB
이 문제는 조합과 백트래킹을 필요로 하는 문제입니다. 그럼 바로 보도록 하겠습니다.
N개의 숫자를 조합해서 K의 수를 표현할 수 있는 가지수를 나타내는 문제입니다.
그렇다면 당연히 조합이 필요하겠죠?
또한, N이 20을 넘어가기 때문에 1 ~ 20개의 숫자를 조합으로 구하려고 하는 것은 대단히 오래걸리기에 백트래킹을 사용해야합니다. 지금까지 더한 합이 K를 넘기면 안된다는 조건으로요.
#include <stdio.h>
#include <malloc.h>
void combi(int* arr, int st, int cnt, int temp, int depth);
int* vis;
int N, K, result;
int main(void) {
int T;
scanf("%d", &T);
for (int t = 1; t <= T; t++) {
// 입력
scanf("%d %d", &N, &K);
int* arr = (int*)malloc(sizeof(int) * N);
for (int n = 0; n < N; n++) {
scanf("%d", &arr[n]);
}
// 풀이
result = 0;
vis = (int*)malloc(sizeof(int) * N);
for (int n = 1; n <= N; n++) {
for (int j = 0; j < N; j++) {
vis[j] = 0;
}
combi(arr, 0, 0, 0, n);
}
printf("#%d %d\n", t, result);
}
}
void combi(int* arr, int st, int cnt, int temp, int depth) {
if (cnt == depth) {
if (temp == K) result++;
return;
}
else if (temp > K) return;
else {
for (int i = st; i < N; i++) {
if (vis[i] == 1) continue;
vis[i] = 1;
combi(arr, i, cnt + 1, temp + arr[i], depth);
vis[i] = 0;
}
}
}
주의 깊게 봐야할 곳은 combi라는 함수입니다.
분기점이 총 3가지가 있습니다. 원하고자하는 조합 갯수를 만족했을 때, 지금까지 더한 합이 K를 넘겼을 때 (백트래킹), 이 두 가지가 아닌 경우
3개의 조합을 했을 때, 그 합이 구하고자 하는 값과 같다면, 갯수를 하나 증가시켜주면 됩니다.
여기서 주의깊게 봐야할 곳은 for문과 vis 배열입니다. for문의 시작 부분을 보면 st라는 인수를 시작점으로 하고 있습니다. vis는 combi 함수의 전역으로 사용되고 있습니다.
이 부분을 설명 드리자면, 총 4가지의 수 중 2개를 뽑는 조합을 시도했을때
1 1 0 0
1 0 1 0
1 0 0 1
0 1 1 0
0 1 0 1
0 0 1 1
이런식으로 나옵니다. 여기서 0번 인덱스를 살펴보면 0번 인덱스에서 나올 수 있는 조합의 수를 파악을 한 뒤엔, 절대 다시 한 번 1이 나오면 안됩니다. (이 부분을 vis가 합니다) 또한, 시간 단축을 위해 for 문의 시작 지점을 0이 아닌 인자 st를 사용하면서 조합을 찾는 위치를 기억할 수 있습니다.
이런 식으로 조합을 사용하여 문제를 풀었습니다.
아래는 조금더 간소화 해보았습니다.
#include <stdio.h>
void combi(int n, int r, int st);
int vis[5];
int main(void) {
for (int i = 1; i <= 5; i++) {
for (int j = 0; j < 5; j++) {
vis[j] = 0;
}
combi(5, i, 0);
}
}
void combi(int n, int r, int st) {
if (r == 0) {
for (int i = 0; i < n; i++) {
printf("%d ", vis[i]);
}
printf("\n");
}
else {
for (int i = st; i < n; i++) {
if (vis[i] == 0) {
vis[i] = 1;
combi(n, r - 1, i);
vis[i] = 0;
}
}
}
}
사실 제가 한 방법은 배열을 이용한 조합입니다. vis라는 배열을 사용하며, for문으로 순회까지 하는 조합이니까요.
근데, 사실 문제를 되돌아보면, 저희가 궁금한 건 어떻게 조합의 결과과 궁금한 것이 아니라, 조합된 값들의 합입니다.
즉, 각 숫자들이 더해지는지 혹은 더해지지 않는지만 생각하면 문제를 쉽게 해결할 수 있다는 뜻입니다.
#include <stdio.h>
#include <malloc.h>
void sol(int idx ,int sum);
int N, K, answer;
int* arr;
int main(void) {
// 입력
int T;
scanf("%d", &T);
for (int t = 1; t <= T; t++) {
scanf("%d %d", &N, &K);
arr = (int*)malloc(sizeof(int) * N);
for (int n = 0; n < N; n++) {
scanf("%d", &arr[n]);
}
// 풀이
answer = 0;
sol(0, 0);
printf("#%d %d\n", t, answer);
}
}
void sol(int idx, int sum) {
if (idx == N || sum > K) {
if (sum == K) answer++;
return;
}
sol(idx + 1, sum + arr[idx]);
sol(idx + 1, sum);
}
이 방법은 아까 전의 방법보다 10배 빠른 속도를 보여주는 코드입니다.
이상 2817. 부분 수열의 합 - D3였습니다. ^_^
'알고리즘 문제 풀이 > SW Expert Academy' 카테고리의 다른 글
3816. [Professional] 아나그램 - D4 (0) | 2020.01.27 |
---|---|
4317. 항구에 들어오는 배 - D3 (0) | 2020.01.12 |
2806. N-Queen - D3 (0) | 2020.01.04 |
7510. 상원이의 연속 합 - D3 (0) | 2020.01.01 |