15949. 현석이의 생일
Code Battle ('23 동계 대학생 S/W 알고리즘 특강 사전문제 (1/7(토) 09:00 ~ 1/11(수) 23:50))
본 게시글은 개인 학습 용도로 작성한 게시글입니다.
(문제 출처: SW Expert Academy)
- 전감산기의 동작 원리와 매우 유사하다.
- 입력 받은 두 정수로 현석이에게 선물할 수를 조합해 내려가다가 Borrow앞자리에서 빌려오는 수가 필요할 경우, 이를 어떻게 처리할 것인지가 중요.
- 문제의 조건을 보면 최악의 경우 $N=10^{100000}$이라고 명시되어 있다. 솔직히 말도 안된다고 생각하지만, $O(N)$시간 안에 풀이 하라는 말을 돌려서 표현 한 것이라는 느낌이 든다.
- 가장 큰 자리 수MSB부터 가장 낮은 자리 수LSB까지 차례로 조합하다가 borrow가 필요한 순간(최대 1회) 필요한 만큼 다시 앞 자리 수들을 거슬러 올라가 borrow를 만들어낸다. 이렇게 풀이하면 최악의 경우에도 $T(2N)$에 답을 구할 수 있다.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.StringTokenizer;
public class Solution {
public static void main(String[] args) throws Exception {
new Solution();
}
private final Boolean USE_FILE_INPUT = false;
private BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
private BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
private Solution() throws IOException {
if (USE_FILE_INPUT) {
useFileIO();
}
solve();
close();
}
private void useFileIO() throws IOException {
System.out.println("[Warn] You are using file input stream.");
close();
try {
bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("./test_input.txt")));
bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("./test_output.txt")));
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("File Not Found.");
}
}
private void close() throws IOException {
bufferedReader.close();
bufferedWriter.close();
}
private void solve() throws IOException {
int T = Integer.parseInt(bufferedReader.readLine());
for (Integer testCaseNo = 1; testCaseNo <= T; testCaseNo++) {
bufferedWriter.write("#"+testCaseNo+" "+solveTestcase()+"\n");
}
}
private final String CASE_IMPOSSIBLE = "-1";
private String solveTestcase() throws IOException {
StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
String N = stringTokenizer.nextToken();
// x와 y는 0~9 사이의 범위이므로 단일 문자로 볼 수 있다. (항상 y > x 이다.)
Character x = stringTokenizer.nextToken().charAt(0);
Character y = stringTokenizer.nextToken().charAt(0);
return solveTestcase(N, y, x);
}
/**
* 쟁점: N은 최악의 경우 10^100000 이기 때문에 String 형태를 유지한 채로 연산하는 것이 포인트라고 생각한다.
* digit단위로만 처리하면 되어서 BigInteger로 치환하는 과정이 반드시 필요한 것은 아니므로 String 형식을 유지하며 답을 구하는 것이 가능하다.
* @param answer
* @return
*/
private String solveTestcase(String targetNumberDigits, Character bigComponentDigit, Character smallComponentDigit) {
StringBuilder answerStringBuilder = new StringBuilder();
Character targetDigit;
Boolean isMSB;
Boolean hasCarry = false;
for (int i = 0; i < targetNumberDigits.length(); i++) {
targetDigit = targetNumberDigits.charAt(i);
isMSB = i == 0;
if (hasCarry || targetDigit >= bigComponentDigit) {
answerStringBuilder.append(bigComponentDigit);
hasCarry |= targetDigit > bigComponentDigit;
continue;
}
if (isMSB && smallComponentDigit == '0') {
hasCarry = true;
continue;
}
if (targetDigit >= smallComponentDigit) {
answerStringBuilder.append(smallComponentDigit);
hasCarry |= targetDigit > smallComponentDigit;
continue;
}
if (isMSB) {
hasCarry = true;
continue;
}
// 앞자리 수로 돌아가서 캐리를 만들어와야하는 경우.
while (true) {
i--;
// 더 이상 앞에서 캐리를 가져올 수 없는 경우.
if (i < 0) {
return CASE_IMPOSSIBLE;
}
// 앞자리에서 캐리를 만들어오는 과정
Character removedDigit = answerStringBuilder.charAt(i);
answerStringBuilder.deleteCharAt(i);
// 큰 수를 작은 수로 다운그레이드
if (removedDigit == bigComponentDigit) {
answerStringBuilder.append(smallComponentDigit);
break;
}
// ...혹은, 가장 첫 숫자가 작은 수면 이를 제거하는 대신 다음 자릿수에서 캐리로 사용 가능
if (i == 0 && removedDigit == smallComponentDigit) {
break;
}
// 그 외에는 더 앞자리 수에서 가져오는 방법 밖에 없음.
};
hasCarry = true;
}
String answer = answerStringBuilder.toString();
if (isValidAnswer(answer)) {
return answer;
}
return CASE_IMPOSSIBLE;
}
private Boolean isValidAnswer(String x) {
return !x.isEmpty();
}
}
문제 풀이를 위해 직접 생성한 테스트케이스도 함께 첨부:
12
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 0 1 : #1 큰 수에 대한 처리
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 1 2 : #2 큰 수에 대한 캐리 처리
1 1 2 : #3 x로 만들어지는 경우
1 0 1 : #4 y로 만들어지는 경우
12 1 2 : #5 x, y로 만들어지는 경우
10 0 1 : #6 x, y로 만들어지는 경우
0 1 2 : #7 만들 수 없는 경우
0 0 1 : #8 만들 수 있는 수가 양의 정수가 아닌 경우
20 1 2 : #9 앞 자릿수를 낮추어 캐리를 빌려와야 하는 경우.
10 1 2 : #10 첫 번째 자리수의 캐리를 빌려와 첫 번째 자리수가 없어지는 경우.
210 1 2 : #11 앞 자릿수를 낮추어도 캐리를 빌릴 수 없어, 더 앞 자리수에서 캐리를 빌려와야 하는 경우.
2110 1 2 : #12 연속된 여러 자릿수로부터 캐리를 빌려와야하는 경우.
위 테스트케이스들의 정답:
#1 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
#2 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
#3 1
#4 1
#5 12
#6 10
#7 -1
#8 -1
#9 12
#10 2
#11 122
#12 1222