Skip to main content Link Menu Expand (external link) Document Search Copy Copied

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

Back to top