문제
https://www.acmicpc.net/problem/1019
정답
import sys
# 0부터 9까지 각 숫자의 개수를 저장할 배열
ans = [0] * 10
def calc(n, ten):
"""
n이라는 숫자 자체에 포함된 각 자리수를
ten 만큼 곱해서 ans에 더합니다.
(예: calc(123, 1) -> ans[1]+=1, ans[2]+=1, ans[3]+=1)
(예: calc(12, 10) -> ans[1]+=10, ans[2]+=10)
"""
while n > 0:
ans[n % 10] += ten
n = n // 10
def solve(start, end, ten):
"""
[start, end] 범위 내의 숫자들을 세는 재귀 함수.
ten은 현재 계산하는 자릿수의 가중치 (1, 10, 100...)
"""
# 1. start를 0으로 끝나는 수로 맞추기
# ex: start=123 -> 123, 124, ..., 129까지는 따로 계산
while start % 10 != 0 and start <= end:
calc(start, ten)
start += 1
# 만약 start가 end보다 크다면
# 이미 계산이 끝났으므로 종료
if start > end:
return
# 2. end를 9로 끝나는 수로 맞추기
# ex: end=456 -> 456, 455, ..., 450까지는 개별 계산
while end % 10 != 9 and start <= end:
calc(end, ten)
end -= 1
# (이 시점에서 start는 0으로, end는 9로 끝남)
# ex: [130, 449]
# 3. [start, end] 까지의 *1의 자리수*만 계산
# 예: [130, 449] -> 10의 자리 이상은 [13, 44]
# 블록의 개수 (cnt) = (44 - 13 + 1) = 32
cnt = (end // 10 - start // 10 + 1)
# 32개의 블록(130~139, 140~149, ..., 440~449)이 있음
# 각 블록은 1의 자리에 0~9를 모두 포함
# 따라서 0~9는 각각 (cnt * ten) 만큼 등장
for i in range(10):
ans[i] += cnt * ten
# 4. 다음 자릿수 재귀 호출
# 1의 자리는 모두 계산했으니,
# [130, 449] -> [13, 44] 범위와
# 가중치 10을 넘겨서 재귀 호출
solve(start // 10, end // 10, ten * 10)
n = int(input())
solve(1, n, 1) # 1부터 N까지
print(' '.join(map(str, ans)))
풀이
N이 최대 10억이기 때문에, 모든 수를 순회하며 각 자리 숫자의 개수를 세는 방법은 불가능하다.
숫자를 나열하다보면 규칙이 보이는데, 0~9까지의 묶음으로 묶어서 세는 것이 가능하다는 것이다.
예를 들어, 10~39까지의 수를 센다고 해보자.
10~19까지 일의 자리 수에서 0부터 9까지는 각각 1번씩 등장한다.
20~29, 30~39까지도 마찬가지이다.
따라서 일의 자리수를 먼저 센다면, 0부터 9까지에 각각 3을 더해준다고 볼 수 있다.
그 다음 십의 자리수를 센다면, 10~19에서는 1이 10번, 20~29에서는 2가 10번.. 이 나온다고 볼 수 있다.
이 규칙을 찾아내는 것은 어렵지 않지만, 코드로 구현하는 것에서 애를 많이 먹었다.
아직 구현 문제에 익숙하지 않다면, 너무 오랜 시간 혼자만의 방법으로 구현하지 말고, 풀이를 보고 이해해 보고, 나중에 시간이 지난 후 다시 풀어 이해도를 점검해보도록 하자.
먼저 나는 재귀를 이용해서 풀어야겠다고 생각했다. 반복되는 규칙이 보였기 때문이다.
그 규칙은, 0~9까지의 범위로 쪼개면, *10^(N) 단위로 묶어서 셀 수 있다는 것이었다.
이를 위해 정답 함수를 크게 다음과 같은 구조로 만들었다.
Pseudo 코드
solve(start, end, ten):
- start 의 일의 자리 수가 0이 될 때까지 start를 1 증가시킨다. 그리고 이 과정을 거치게 되는 수는, 각각의 숫자가 몇 번 등장하는지 직접 세어준다.
- end 의 일의 자리 수가 9가 될 때까지 end를 1 감소시킨다. 그리고 이 과정을 거치게 되는 수는, 각각의 숫자가 몇 번 등장하는지 직접 세어준다.
- 0으로 끝나는 start 와 9로 끝나는 end 사이의 1의 자리 수가 몇 번 등장하는지 세어준다.
- 다음 재귀 함수인 solve(start // 10, end // 10, ten * 10) 을 호출한다.
이해하기 어려운 것이 정상이다. 직접 예를 들어보면 이해가 수월할 것이니 이해가 어렵다면 예를 들어보기를 추천한다.
예를 들어, solve(1, ABCD, 1) 을 처음에 호출했다고 하자.
1번에 따라, 1을 10으로 올려준다. 그리고 그 과정을 거치는 1부터 9까지의 수는 ten 만큼 더해준다. 즉
ans[1] += 1, ans[2] += 1 ... ans[9] += 1 이 된다.
2번에 따라, ABCD 에서 D가 9가 될 때까지, ABCD 에서 1씩 줄여준다. 그리고 그 과정을 거치는 수도 ten 만큼 더해준다.
ans[ABCD] += 1, ans[ABCD - 1] += 1 ... ans[ABC0] += 1
1,2 번이 끝나고 나서 바뀌게 된 start 와 end 를 각각 new_start, new_end 라고 하겠다.
new_start = 10
new_end = ABE9 가 되어 있을 것이다.
앞으로의 재귀호출 함수에서 신경쓸 것은 [new_start,new_end] 에 있는 숫자를 세는 것이다. 앞서 버린(?) 숫자들(1~9, ABE9~ABCD)은 이미 그 개수를 다 세었음에 유의해야 한다.
3번의 핵심 포인트는, 한 번의 재귀 호출 함수에서 일의 자리 수"만" 센다는 것이다.
즉 [10, ABE9] 까지의 수에서 우리가 셀 것은, 일의 자리에 있는 0~9까지의 수에 한정된다.
십의 자리수, 백의 자리수, 천의 자리수는 다음의 재귀호출에서 센다.
이 관점에서 보면, 일의 자리수를 0~9 까지의 묶음으로 묶어볼 수 있다.
10~19
20~29
...
ABE0~ABE9
그럼 이때 1의 자리 개수는?
10~ 단위에서 1개씩,
20~ 단위에서 1개씩,
...
ABE~ 단위에서 1개씩 나오므로,
각각 ABE-1+1 이 된다.
ex) ans[0] += ABE-1+1 , ans[1] += ABE-1+1, ... ans[9] += ABE-1+1
즉 (new_end // 10) - (new_start//10) + 1 로 표현된다.
자, 그럼 지금까지 우리가 센 숫자는 무엇인가?
1 부터 new_start -1까지의 숫자의 모든 자리수,
new_end +1 부터 ABCD 까지의 숫자의 모든 자리수,
그리고 [new_start, new_end] 범위의 일의 자리 수를 모두 세어주었다.
그럼 앞으로 세어야 할 것은 무엇인가?
[new_start, new_end] 범위의 일의 자리 수를 제외한 나머지 모든 자리수를 세어주면 된다.
따라서 4번을 실행할 때, 첫 번째와 두 번째 인자로
solve(new_start // 10, new_end // 10, *) 을 넣는 이유가 이해가 될 것이다.
그럼 ten 에는 어떤 수가 들어가야 할까?
지금까지 우리가 푼 예제는, 1의 자리 수였기 때문에,
별도의 ten 을 곱하지 않아도 되었다.
하지만 십의 자리 수 이상을 셀 때는 이야기가 다르다.
예를 들어, 위 예제에서 solve(1, ABE, 10) 을 호출했다고 가정해보자.
그럼 1번을 수행하기 위해
1 -> 10을 만들 때, 숫자를 1번씩만 더해주면 될까?
잊지 말아야 할 것이, ABE 에서 E는 사실 10의 자리라는 것이다.
즉
10
11
12
...
19
에서의 십의 자리에서의 1의 개수를 세는 것이다. 이때 십의 자리에서 1의 개수는 1개인가? 아니면 10개인가?
10개이다.
따라서
+= 10 을 해줘야 한다.
2번도 마찬가지.
3번은 어떨까?
마찬가지로 [new_start, new_end] 의 "일의 자리" 를 볼 것이다. new_start 와 new_end 에서는 일의 자리이지만, 사실 우리가 지금 세고자 하는 것은 ABCD에서 십의자리 수에 해당한다.
예를 들어 new_start 가 10, new_end 가 169 이라고 하자.
[10, 169]
그럼 일의 자리의 0~9는 각각 몇 번씩 반복될까? 라는 질문에 앞서 했던 것처럼
169 - 10 + 1 = 160 이라고 대답할 수 있을 것이다.
10 ~ 19
20 ~ 29
...
160 ~ 169
를 보면 그러하니까.
그런데 사실 우리가 세는 건 진짜 10~169까지의 일의 자리의 수를 세는 것이 아니었다!!
우리가 세는 건 사실, 10X ~ 169X 에서 십의 자리 수를 세는 것이었기 때문에,
10 에서, 0이 사실 1개가 아니라 10개 였다는 것을 알 수 있다.
100
101
102
...
109 에서 10의 자리 수의 0은 10개기 때문에!!
따라서
(160 - 10 + 1) * 10(==ten) 을 한 값을 ans 의 0부터 9까지의 수에 더해줘야 한다.
백의 자리 수를 셀 땐 그럼 ten 에 몇을 넘겨줘야 할까? 100을 넘겨주면 된다.
'알고리즘' 카테고리의 다른 글
| 백준 골드1 11689번 Python (0) | 2025.11.06 |
|---|---|
| 백준 육각수 시간 복잡도 관련 쓰기 (temp!) (0) | 2025.11.05 |
| 백준 9252 파이썬 python 재귀 풀이 (3) | 2025.08.02 |
| 백준 17141 파이썬 (1) | 2025.07.16 |
| 백준 13398 연속합2 파이썬 Python py (재귀 풀이) (0) | 2025.07.03 |
댓글