
Brute Force 방식으로 풀어야 한다. 사실 이게 브루트 포스 방식인지 몰랐는데.. 내가 학교에서 푼 코딩은 대부분 브루트 포스였구나 하고 깨달았다. 아무튼~
<문제 분석>
T : 변수 K를 입력받는 횟수
K : 3개의 삼각수인지 확인할 변수
Tn[ ] : Tn을 미리 구해서 저장해놓은 배열
생각한 방식은.. 1. Tn을 미리 구해놓는다. 2. 재귀를 이용해 K - Tn을 한다. 3. K - Tn을 할 때마다 횟수를 더해준다. 간단히 요약하면 이렇게 된다. 다른 쉬운 풀이방법도 많은데 재귀가 더 익숙해서 재귀로..
1. Tn을 미리 배열에 저장해둔다
Tn = n * (n + 1) / 2 이므로 그냥 계산해서 미리 배열에 저장하면 된다. 그럼 몇번째 Tn까지 저장하면 될까..? 문제에서 K는 1000이하라고 했으므로 ' Tn <= 1000 ' 을 만족하는 가장 큰 Tn까지 저장하면된다. 계산을 해보니 .. n = 44 일때 Tn은 990으로 가장 적합하다. 45*46/2 는 1035로 1000을 넘어버린다.
T1 T2 ... T43 T44 까지 저장하자. Tn[1] = T1 이런식으로 쓰고싶어서 for문의 i=1 부터 시작했다.
int[] Tn = new int[45];
// Tn 배열 생성
for(i=1;i<45;i++){
Tn[i] = (i * (i + 1)) / 2;
}
2. K - Tn (CheckThreeTn 함수)
K보다 작은 Tn들 중 제일 큰 값을 먼저 뺀다. 거스름돈 문제랑 비슷하게.. 마치 우리가 900원을 거슬러줄때 100원 9개를 주는 것이 아니라, 500원 1개, 100원 4개를 주듯이..
for 문을 이용해 Tn[i]가 K보다 커지는 순간을 찾는다. 그때 Tn[i - 1]이 K보다 작은 Tn들 중에 가장 큰 값이다. i - 1을 n에 저장해준다.
for(i=1;i<45;i++){
if(Tn[i] > K)
break;
}
// K보다 작으면서 제일 큰 Tn[i] 인덱스 저장
n = i - 1;
3. 재귀호출 (CheckThreeTn 함수)
K 에서 Tn을 빼줄거다. 근데 제일 처음에 뺄 수는 CheckThreeTn 함수에서 정해준다.
예를들어, 10은 T4와 T3+T2+T1로 표현할 수 있다. (T4 = 10, T3 = 6, T2 = 3, T1 = 1) 이때 T4로 표현되는 것만 확인하고 끝내버리면 T3+T2+T1로 표현할 수 있다는걸 확인못하니까 .. 첫번째로 뺄 Tn이 T1이 될때까지 확인한다. 단, 3개의 Tn의 합으로 이뤄지는 경우를 찾았을때만 제외하고! (ans == 1)
빼면서 확인하기 때문에 이런식으로 코드를 짰다. recursiveCase() 이건 recursiveCase 함수를 호출해서 그 안에서 돌아간다는 뜻.. 표시 없는건 CheckThreeTn 함수이다. K = 10 으로 예를 들면..
n = 4, i = 4
recursiveCase(K - T4 = 0 임을 확인함) -> recursiveCase(return 0;) -> ans = 0
i--
n = 4, i = 3
recursiveCase(K(첫 재귀때 K는 10) - T3(6) = 4) -> recursiveCase(K(재귀하면서 값이 바뀌어서 K는 4) - T2(3) = 1) -> recursiveCase(K(1) - T1 = 0) -> recursiveCase(basecase의 조건에 맞으므로 return 1;) * 3번 -> ans = 1
ans 가 1이므로 더 진행하지 않고 끝냄(break;)
return ans;
이런식이다. 헷갈리는데.... recursiveCase 함수를 보면 이해가 될거다.
public static int recursiveCase(int K, int[] Tn, int n, int cnt){
int i;
// base case
if(K == 0 && cnt == 3) // 3개의 Tn의 합일때
return 1;
if(K == 0 || cnt > 3) // Tn을 3번 빼지 않았는데 이미 K가 0이 된 경우, Tn을 뺀 횟수가 3을 넘어가는 경우 더 계산할 필요 X
return 0;
// recursive case
for(i=n;i>0;i--){
if(K - Tn[i] >= 0){
if(recursiveCase(K - Tn[i], Tn, n, cnt + 1) == 1) // 3개의 Tn의 합일때
return 1;
}
}
return 0; // 3개의 Tn의 합이 아닐때
}
public static int checkThreeTn(int K, int[] Tn){
int i, n, ans = 0;
for(i=1;i<45;i++){
if(Tn[i] > K)
break;
}
// K보다 작으면서 제일 큰 Tn[i] 인덱스 저장
n = i - 1;
for(i=n;i>0;i--){
ans = recursiveCase(K, Tn, n, 0);
if (ans == 1) // 3개의 Tn의 합일때
break;
}
return ans;
}
아래는 전체 코드이다.
import java.io.*;
public class BOJ10448 {
public static int recursiveCase(int K, int[] Tn, int n, int cnt){
int i;
// base case
if(K == 0 && cnt == 3) // 3개의 Tn의 합일때
return 1;
if(K == 0 || cnt > 3) // Tn을 3번 빼지 않았는데 이미 K가 0이 된 경우, Tn을 뺀 횟수가 3을 넘어가는 경우 더 계산할 필요 X
return 0;
// recursive case
for(i=n;i>0;i--){
if(K - Tn[i] >= 0){
if(recursiveCase(K - Tn[i], Tn, n, cnt + 1) == 1) // 3개의 Tn의 합일때
return 1;
}
}
return 0; // 3개의 Tn의 합이 아닐때
}
public static int checkThreeTn(int K, int[] Tn){
int i, n, ans = 0;
for(i=1;i<45;i++){
if(Tn[i] > K)
break;
}
// K보다 작으면서 제일 큰 Tn[i] 인덱스 저장
n = i - 1;
for(i=n;i>0;i--){
ans = recursiveCase(K, Tn, n, 0);
if (ans == 1) // 3개의 삼각수 합으로 이뤄질때
break;
}
return ans;
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int T, K, i;
int[] Tn = new int[45];
// Tn 배열 생성
for(i=1;i<45;i++){
Tn[i] = (i * (i + 1)) / 2;
}
T = Integer.parseInt(br.readLine());
for(i=0;i<T;i++){
K = Integer.parseInt(br.readLine());
bw.write(checkThreeTn(K, Tn) + "\n");
bw.flush();
}
}
}
늘 C언어로 재귀를 짰는데 자바로 재귀코드를 짜니까 포인터가 없어서 너무 불편했다. 지금 보면서도 return을 너무 많이 하는 것 같고.. 뭔가 지저분해보이는데 어떻게 더 수정을 못하겠다 ㅠㅠ.. 처음엔 메인에서 포인터로 &cnt를 보내고 cnt를 리턴하고 받은 cnt로 조건에 맞는지 확인하려고 했는데... 자바로는 그게 안되더라.. 아니 내가 못한걸지도.. 전역변수를 쓰면 되긴하지만 쓰기싫었다.
'코딩가딩가' 카테고리의 다른 글
| [JAVA]BOJ 11068, 회문인 수 (0) | 2024.07.17 |
|---|---|
| [JAVA]BOJ 11005, 진법 변환 2 (0) | 2024.07.13 |
| [C]BOJ 9095, 1, 2, 3 더하기 (0) | 2024.07.12 |
| [C]BOJ 1748, 수 이어 쓰기 1 (0) | 2024.07.12 |
| [JAVA]BOJ 3273, 두 수의 합 (0) | 2024.07.10 |