포스트

(BOJ)-1407, Python

(BOJ)-1407, Python

문제 요약

출처: (BOJ)1407 - 2로 몇 번 나누어질까

두 수 $A,B$가 주어졌을 때, $A \le A+1, A+2, …, B-1 \le B$에 포함되는 모든 자연수에 대해서 각각 $n$번씩 $2$로 나눌 수 있다고 하자. $2^n$의 합을 구하는 문제이다.

  • $f(15)$, $15$는 $2$로 한 번도 나누어질 수 없기에 $2^0=1$
  • $f(40)$, $40$은 $2$로 세 번 나누어질 수 있기에 $2^3=8$
  • $1≤A≤B≤10^{15}$

접근 과정

1. 완전탐색 접근

모든 수가 $2$로 몇 번 나누어지는지에 대한 지수 $n$을 담을 수 리스트를 생성하고, 각 지수를 계산하는 방식을 두가지 방법으로 작성했다. 그러나 아래 두 코드 각각 메모리 초과,시간 초과문제가 발생했다. 두 코드 모두 극단적인 경우인 $A=1, B= 10^{15}$에서는 당연히 문제가 발생할 것이다. 어느정도 예상됐다..

코드 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#지수를 계산하는 부분에서 재귀 사용(메모리 초과)
A, B = map(int, input().split())
# numList = list(i for  i in range(A, B+1))
power = [0 for _ in range(B-A+1)] #모든 수 각각의 지수를 담아놓을 리스트

def recur(number, idx): 
    if number % 2 == 0:
        power[idx] += 1
        recur(number//2, idx)
        return
    return 
answer = 0
#for idx, i in enumerate(numList):
for idx, i in enumerate(range(A, B+1)):
    recur(i, idx)
    answer += 2**power[idx]
print(answer)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#지수를 계산하는 과정에서 반복문 사용(시간 초과)
A, B = map(int, input().split())
# numList = list(i  for  i  in  range(A, B+1))

def check(number):
	idx = 0
	while number % 2 == 0:
		idx += 1
		number = number //2
	return idx
answer = 0
# for i in numList:
for	i in range(A, B+1):
	answer += 2**check(i)
print(answer)

2. 정수론 최적화

위 접근 방법으로도 안된다면, 분명 최적화할 수 있는 수학적인 방법이 있을거라고 생각했다. 그래서 규칙을 테이블을 통해 찾아보도록 했다. 다음은 $1$부터 $8$까지의 숫자에 대해 $2^n$으로 나누어질 경우를 $1$, 그렇지 않을 경우를 $0$으로 표시한 테이블이다.

 12345678
$2^0$11111111
$2^1$01010101
$2^2$00010001
$2^3$00000001

여기서 신기했던 규칙을 하나 찾을 수 있었다..!

  • \[8// 2^0 = 8개\]
  • \[8// 2^1 = 4개\]
  • \[8// 2^2 = 2개\]
  • \[8// 2^3 = 1개\]

이 몫들은 특정 자연수 $1$부터 $8$까지의 $2^n$의 약수로 포함될 수 있는 모든 수의 개수라는 것이다. 다시 말해, $8$을 $2^n$으로 나누었을 때의 몫은 $1$부터 $8$까지의 모든 수 중에서 $2^n$을 약수로 가지는 수의 개수이다!! 이를 자연수 $A$에 관한 식으로 나타낸다면 아래와 같다.

  • \[(A//2) * (2-1)\]
  • \[+(A//4) * (4-2)\]
  • \[+(A//8) * (8-4)\]
  • \[+.........\]
  • \[+(A//2^n) * (2^n-2^{n-1})\]

그러면 누적합으로 계산하니 $B$를 계산한 결과에서 $A-1$의 결과를 빼면 되겠다!

코드 구현

1
2
3
4
5
6
7
8
9
10
11
A, B = map(int, input().split())
A -=1
tmp_A = A #2^0 = 1은 모든 자연수가 가지고 있는 수이므로 
for i in range(1, 99):
    tmp_A += (A//(2**i)) * ((2**i) - (2**(i-1)))
    
tmp_B = B
for i in range(1, 99):
    tmp_B += (B//(2**i)) * ((2**i) - (2**(i-1)))

print(tmp_B - tmp_A)  

회고

수학적 사고력을 바탕으로 접근한다는 것이 많이 어려웠고, 시간 제한, 메모리 제한으로 시간, 공간 복잡도를 어느 정도 계산하고 접근 방법을 정할 수 있는 실력까지 되게끔 더 많은 노력을 해야겠다고 깨달았다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.