1. 단위분수
분자가 1이고 분모가 양의 정수인 분수
1/1, 1/2, 1/3,...이 단위분수이다.
1/(1.5)라든지 1/(3.4)같이 분모가 실수이면 단위분수가 아니다.
흥미로운 점은 모든 유리수는 어떤 단위분수들의 합으로, 여러가지 방법으로 나타낼 수 있다는 점이다.
$$\frac{4}{5} = \frac{1}{2} + \frac{1}{4} + \frac{1}{20} = \frac{1}{3} + \frac{1}{5} + \frac{1}{6} + \frac{1}{10}$$
단위분수들의 합을 이집트 분수(Egyptian fraction)라고 부른다
이렇게 나타내는 방법에 대해 여러가지 연구들이 있다..
https://en.wikipedia.org/wiki/Egyptian_fraction
Egyptian fraction - Wikipedia
From Wikipedia, the free encyclopedia Finite sum of distinct unit fractions The Rhind Mathematical Papyrus An Egyptian fraction is a finite sum of distinct unit fractions, such as 1 2 + 1 3 + 1 16 . {\displaystyle {\frac {1}{2}}+{\frac {1}{3}}+{\frac {1}{1
en.wikipedia.org
실용적으로 피자 5판을 8명이서 나눠먹는다고 할때, 각 사람은 피자를 얼마나 받아야하는가?
$$\frac{5}{8} = \frac{1}{2} + \frac{1}{8}$$이므로, 피자 반판 + 피자 1/8판을 받아야한다는 것을 의미한다.
그렇다면 관심이 생기는 부분은 이러한 이집트 분수 표현법을 어떻게 찾을 수 있을까?
여러가지 방법이 있지만 알고리즘적으로 생각할 수 있는 좋은 방법 중 하나가 피보나치의 그리디 알고리즘이다.
주어진 분수 m/n에서 뺄 수 있는 가장 큰 단위 분수를 0이 될때까지 빼면 된다.
예를 들어 9/20은 9/20 - 1/3 = 7/60
7/60 - 1/9 = 1/180
1/180 - 1/180 = 0
이므로, 9/20은 1/3 + 1/9 + 1/180이다.
2. 연습문제 1
위의 피보나치 그리디 알고리즘에 따라 주어진 유리수를 단위분수의 합으로 나타내는 문제이다.
그런데, 이 그리디 알고리즘은 단위분수의 분모가 매우 커질 수 있다는 문제가 있다.
그래서 단위분수를 빼고 난 뒤에 나온 분수의 분모가 1000000보다 작게 제한을 두고자 한다.
from sys import stdin
def gcd(a,b):
while b != 0:
a,b = b,a%b
return a
upper = 1000000
while 1:
m,n = map(int,stdin.readline().split())
if m == 0 and n == 0:
break
answer = []
while 1:
j = n/m
if n % m != 0:
j = int(j) + 1
else:
j = int(j)
for i in range(j,upper):
#m/n - 1/i = (mi - n)/ni
mm = m*i-n
nn = n*i
if mm >= 0:
g = gcd(mm,nn)
mm = mm//g
nn = nn//g
if nn < upper:
answer.append(i)
j = i
m = mm
n = nn
break
if m == 0 and n == 1:
break
print(*answer)
분수 m/n이 주어질때, 1/1, 1/2, ..., 1/1000000 을 차례대로 빼봐서, 처음으로 분모 제한안에 들어오면 1/D1이라 하고 그걸 선택
그리고 나서는 1/D1, 1/D1+1, 1/D1+2,..., 1/1000000을 차례대로 빼서 처음으로 분모 제한안에 들어오면 1/D2
....
이런식으로 범위를 좁혀나가면서 다 해보면 된다.
그런데 이러면 시간이 좀 걸릴 수 있는데 그나마 첫 시작점을 빠르게 잡을 수 있다
m/n에서 뺄 수 있는 가장 큰 단위분수 1/d라고 한다면 m/n >= 1/d인데, 1/d가 가장 커야하므로 d가 최솟값이어야한다.
여기서 역수를 취하면 n/m <= d이다.
따라서 d는 n/m이상의 가장 작은 정수이다.
그러므로 현재 분수 m/n에 대하여 n/m = x.a1a2a3... 이런식으로 나눠보고 이거 이상의 가장 작은 정수를 찾는다.
이는 n이 m으로 나누어 떨어지면 n//m일거고 n이 m으로 나누어 떨어지지 않으면 n//m+1일 것이다.
upper = 1000000
while 1:
m,n = map(int,stdin.readline().split())
if m == 0 and n == 0:
break
answer = []
while 1:
j = n/m
if n % m != 0:
j = int(j) + 1
else:
j = int(j)
이러한 i = j부터 i = upper = 1000000까지의 단위분수에 대해 시도해본다.
이때 m/n - 1/i = (m*i-n)/n*i일건데, 이를 gcd를 구해서 기약분수로 나타내고
그 기약분수의 분자 mm이 0 이상의 양수여야하고, 분모 nn은 upper보다 작아야한다.
이를 만족하면 바로 통과하고 그 단위분수를 선택
위 과정을 0이 될때까지 반복
while 1:
j = n/m
if n % m != 0:
j = int(j) + 1
else:
j = int(j)
for i in range(j,upper):
#m/n - 1/i = (mi - n)/ni
mm = m*i-n
nn = n*i
if mm >= 0:
g = gcd(mm,nn)
mm = mm//g
nn = nn//g
if nn < upper:
answer.append(i)
j = i
m = mm
n = nn
break
if m == 0 and n == 1:
break
3. 연습문제 2
이번엔 주어진 분수의 단위분수 분할의 개수를 구하는 문제
여기서 2/3 = 1/2 + 1/6인데 이것의 순서를 바꾼 1/6 + 1/2는 서로 같은 것이라고 친다.
먼저 주어진 분수 p/q를 기약분수로 나타낸다.
def gcd(a,b):
while b != 0:
a,b = b,a%b
return a
p,q,a,n = map(int,input().split())
g = gcd(p,q)
p = p//g
q = q//g
초기값을 y/x = 0/1이라 두고 1/1, 1/2,... 순서대로 하나하나 더해가며 DFS로 답을 찾는다.
먼저 핵심은 현재 1/3을 선택해서 dfs를 진행한다면, 다음에는 다시 1/1,1/2,..부터 시작하는게 아니라
1/3,1/4,1/5...부터 시작해야하는 것이다.
왜냐하면 1/2 + 1/6과 1/6 + 1/2는 서로 같은것으로 치니까 분모를 작은것부터 큰 순서대로 정렬한다는 거지
그리고 1/i를 선택했다면 조건에 따라 이전까지 선택한 단위분수들의 분모 v에 대하여 v*i가 a이하여야한다.
그렇게 선택했다면, 현재 분수 합 y/x + 1/i = (yi + x)/xi이므로, 이를 기약분수로 나타내 yy/xx라고 하자.
이제 다음 dfs로 들어가면 된다.
만약 v*i > a이면, 그것보다 큰 i를 선택하더라도 무조건 vi > a이므로 for문을 더 돌지말고 return해야한다.
def dfs(y,x,j,v,m):
for i in range(j,a+1):
#y/x + 1/i = (iy + x)/xi
if v*i <= a:
g = gcd(x + y*i,x*i)
yy = (x+y*i)//g
xx = (x*i)//g
dfs(yy,xx,i,v*i,m+1)
else:
return
이때 현재 선택된 분수의 개수 m개가 n개 이하이면, 조건을 모두 만족하고 있으므로,
현재 값 y/x가 목표로 하는 p/q와 동일한지 체크해야한다. 모두 기약분수니까 y = p, x = q이면 카운팅
그런데 현재 m = n이면 더 이상 진행할 필요가 없으므로 return해준다.
마찬가지로 분수를 계속 더해가고 있으므로 y/x >= p/q이면 더 할 필요가 없다.
이는 정수 계산으로는 yq >= xp이다.
count = 0
def dfs(y,x,j,v,m):
global count
if m <= n:
if y == p and q == x:
count += 1
if m == n:
return
#y/x > p/q
#qy > px
if q*y >= p*x:
return
for i in range(j,a+1):
#y/x + 1/i = (iy + x)/xi
if v*i <= a:
g = gcd(x + y*i,x*i)
yy = (x+y*i)//g
xx = (x*i)//g
dfs(yy,xx,i,v*i,m+1)
else:
return
dfs(0,1,1,1,0)
print(count)
'정수론' 카테고리의 다른 글
| 에라토스테네스의 체 변형 segmented sieve 배우기 (0) | 2025.06.30 |
|---|---|
| 의외로 모르는 어떤 분수가 유한소수가 될 수 있는 조건 (0) | 2025.04.29 |
| 정수의 임의의 거듭제곱들의 합을 바라보는 놀라운 방법 (0) | 2025.02.11 |
| 나눗셈 실수오차가 생긴다면 직접 나눗셈을 구현하기 (0) | 2024.09.26 |
| 10진수를 다른 진법으로 바꾸는 방법 (0) | 2024.09.01 |
