본문 바로가기

Problem Solving/백준

백준 1019 책 페이지

https://www.acmicpc.net/problem/1019

 

1019번: 책 페이지

첫째 줄에 0이 총 몇 번 나오는지, 1이 총 몇 번 나오는지, ..., 9가 총 몇 번 나오는지를 공백으로 구분해 출력한다.

www.acmicpc.net

숫자 N이 주어졌을 때 1(첫 페이지)부터 N까지 쭉 순회하면서 등장한 숫자를 세는 건 당연히 안된다. 그 방법으로는 절대로 안된다는 걸 직관적으로 알려주기 위해 N의 상한이 10억이다...

 

그러면 당연히 숫자가 주어졌을 때 그 숫자만 보면서 답을 구할 수 있지 않을까라는 생각을 해봐야 한다. 

 

필자가 생각한 방법은 주어진 숫자를 첫자리, 중간자리, 끝자리 이렇게 3개의 파트로 나누는 것이다.

예를 들어, 3516라는 숫자가 주어졌다고 하자. 그러면 첫자리는 3이고 중간자리는 51이고 끝자리는 6이다.

각 자리에 올 수 있는 0부터 9까지 숫자를 세어 누적해주는 것이다.

 

첫자리는 첫자리뒤에 있는 수+1 만큼 등장한 것이 보장된다. 

3은 516+1 = 517번 등장한 것이다. 

3000부터 3516는 516+1번이기 때문이다.

그리고 주의할 부분은 3뿐만 아니라 1,2도 1000번씩 등장한다는 점이다.

3000에 이르기 위해서는 1000~1999를 거쳤고, 2000에서 2999까지를 거쳤기 때문이다.

즉, 1부터 3미만의 숫자들은 곱하기 10^(현재 자리수, 이 경우엔 3)만큼씩 등장했다.

그 이외의 숫자는 첫자리에서 등장하지 않는다.

temp = int(n[1:])  <--- 뒷자리의 숫자를 찾기 위함.
ans[e] += temp+1   <--- 해당자리의 숫자는 temp+1만큼 추가
for j in range(1, e):
	ans[j] += 10**(sz-1)   <--- 1부터 3미만의 숫자들은 곱하기 10^(현재 자리수, 이 경우엔 3)만큼씩 등장

 

그리고 중간자리에 앞서 마지막 자리를 생각해보자. 이 경우엔 6가 된다.

3516까지 오기까지 6, 16, 26, ...이런식으로 거쳐왔을테고, 그렇다면 351+1번 등장했다는 걸 알 수 있다.

즉 첫자리를 처리한 것처럼, 마지막 자리 숫자를 떼어낸 숫자 (351) + 1이 6가 등장한 횟수가 된다.

그리고 첫자리 처리때와 마찬가지로 6미만의 숫자도 똑같이 351+1번 등장했다.

3516 바로 앞에 3515이 있었고, 이 3515도 5, 15, 25, ...이렇게 거쳐왔기 때문이다.

0은 351+1이 아닌 351번이다. 왜냐면 첫페이지는 항상 1이기 때문이다. 주의할 부분이 많다; 

아직도 남은 부분이 있다. 아까 첫자리를 처리할 때는 3보다 큰 수는 생각하지 않았다. 왜냐하면 등장하지 않았기 때문이다.

하지만 마지막 자리는 다르다. 3516이 있었다면 3507이 있었다... 즉 6보다 큰 7,8,9는 350 + 1번 등장했다. 351+1이 아닌 350+1이다.

temp = int(n[:-1])
ans[0] += temp		<--- "0은 351+1이 아닌 351번이다. 왜냐면 첫페이지는 항상 1이기 때문"
for j in range(1, e+1):
	ans[j] += temp+1
for j in range(e+1, 9+1):
	ans[j] += temp

 

중간자리보다 앞자리와 끝자리를 먼저 처리한 이유는 중간자리가 바로 위 2가지 처리의 합성으로 처리하기 때문이다.

3516에서 5와 1이 중간자리인데, 먼저 5의 자리 백자리를 보자. 

이 자리에서 1은 몇번 나왔을까. 100부터 199까지 100번, 1100에서 1199까지 100번, 2100에서 2199까지 100번, 도합 300번이 되었다. 여기까지는 사실 1부터 9까지 같다. 0은 1000부터 1099, 그리고 2000~2199까지만 가능하므로 (3-1)*100=200번만 등장한다.

이제 여기서 0부터 4까지는 3000~3099, 3100~3199, ...까지의 100번씩을 추가해주어야 한다. 

마지막으로 5는 16+1 번이 추가로 등장한다.

이제 정리하면 중간자리에는 1부터 9까지 앞에 있는 숫자(3) 곱하기 현재 자리수(100)만큼이 일단 등장하고, 0은 그 숫자보다 하나 적은 수 곱하기 자리수만큼이고, 해당 숫자보다 작은 수(0~4)는 자리수만큼을 더 추가해준다. 그리고 해당 자리에 있는 숫자(5)는 뒤에 있는 숫자16 + 1만큼 더 등장한다는 점을 알 수 있다.

temp1 = int(n[:i])
temp2 = int(n[i+1:])

ans[0] += (temp1-1)*(10**(sz-1-i))		<--- "0은 300번이 아닌 200번"
for j in range(1, 10):
	ans[j] += temp1*(10**(sz-1-i))		<--- "도합 300번"

for j in range(e):
	ans[j] += 10**(sz-1-i)		<--- "해당 숫자보다 작은 수(0~4)는 자리수만큼을 더 추가"

ans[e] += temp2+1		<--- "마지막으로 5는 16+1 번이 추가로 등장"

 

차분히 하나하나 짚어서 생각해야 한다. 쉽지 않다고 생각한다.

 

<코드>

 

import sys
si = sys.stdin.readline


def main():
    n = si()[:-1]
    sz = len(n)
    ans = [0 for _ in range(10)]

    if sz == 1:
        for i in range(int(n)+1):ans[i] += 1
        ans[0] -= 1
        print(*ans)
        return

    for i, e in enumerate(n):
        e = int(e)

        if i == sz-1:
            temp = int(n[:-1])
            ans[0] += temp
            for j in range(1, e+1):ans[j] += temp+1
            for j in range(e+1, 9+1):ans[j] += temp

        elif not i:
            temp = int(n[1:])
            ans[e] += temp+1
            for j in range(1, e):ans[j] += 10**(sz-1)

        else:
            temp1 = int(n[:i])
            temp2 = int(n[i+1:])

            ans[0] += (temp1-1)*(10**(sz-1-i))
            for j in range(1, 10):ans[j] += temp1*(10**(sz-1-i))
            for j in range(e):ans[j] += 10**(sz-1-i)

            ans[e] += temp2+1
            
    print(*ans)


if __name__ == '__main__':
    main()