1. 프로베니우스의 동전 문제(Frobenius coin problem)
서로 다른 두 동전 x원, y원이 있을때, 이 두 동전을 각각 적당한 개수를 사용하여, 만들 수 없는 금액중 가장 큰 값은?
이 문제의 정답을 프로베니우스의 수(Frobenius number)라고 부른다.
예를 들어 3원, 5원짜리 동전으로 만들 수 없는 가장 큰 금액은 7원이다.
이 말은 8원 이상의 금액은 무조건 만들 수 있다는 뜻이다.
주의할 점은 7원 밑의 금액 중에서 1,2,4원은 만들 수 없다는 것은 자명하다.
프로베니우스의 수는 x,y가 서로소(최대공약수가 1)일때만 존재한다.
이때, 프로베니우스의 수는 xy - x - y임이 알려져있다.
2. 일반화된 문제
일반적으로는, n개의 동전 $a_{1}, a_{2},..., a_{n}$이 있고, 이들의 최대공약수가 1일때,
$$k_{1}a_{1} + k_{2}a_{2} + ... + k_{n}a_{n}$$으로 만들 수 없는 가장 큰 양의 정수는 얼마인가?
이 문제의 정답을 프로베니우스의 수라고 부른다.
n이 2일때는 명확한 공식이 위에서 언급한 대로 존재하지만, n이 3 이상일때는 명확한 공식이 존재하지 않고 구하는 방법이 알려져있지 않다.
3. 프로베니우스의 수는 존재하는가?
프로베니우스의 수는, $a_{1}, a_{2},..., a_{n}$의 최대공약수가 1인가 아닌가에 따라 존재 여부가 달려있다.
실제로 $k_{1}a_{1} + ... + k_{n}a_{n}$이 만들 수 있는 수는 $a_{1}, ..., a_{n}$의 최대공약수의 배수이다.
이는 베주의 정리에 의해 참이다. 주의할 점은 그 역은 성립하지 않는다.
$a_{1},..., a_{n}$의 최대공약수의 배수라고 하더라도, $k_{1}a_{1} + ... + k_{n}a_{n}$이 만들 수 없을 수 있다.
예를 들어 6원짜리 동전과 14원짜리 동전이 있을때, 이 두 수의 최대공약수는 2이다.
다만, 2의 배수 2,4,8,10은 만들지 못한다.
하지만, 6,14,20,... 등등 만들 수 있는 수는 2의 배수이다.
최대공약수가 1이 아니라면, $k_{1}a_{1} + ... + k_{n}a_{n}$이 만들 수 없는 수는 무한히 존재한다.
예를 들어 6원짜리 동전과 14원짜리 동전으로 홀수는 절대로 만들 수 없다.
다만, 최대공약수가 1이라면, 슈어의 정리(Schur's theorem)에 의해 만들 수 없는 수들의 집합이 유한하여, 프로베니우스의 수가 존재한다.
4. 슈어의 정리(Schur's theorem)
서로소인 정수 집합 $a_{1},a_{2},..., a_{n}$에 대해, $x= c_{1}a_{1} + c_{2}a_{2} + ... + c_{n}a_{n}$을 만족하는
음이 아닌 정수 조합 $c_{1},c_{2},...,c_{n}$의 서로 다른 개수는 x가 커질수록

에 수렴한다.
이 정리의 결과는, 서로소인 정수 집합 $a_{1},...,a_{n}$이 주어질때, 이들의 음이 아닌 정수들의 선형결합
$c_{1}a_{1} + c_{2}a_{2} + ... + c_{n}a_{n} = x$이 존재하여,
이보다 더 큰 수는 $a_{1},...a_{n}$의 선형결합으로 만들 수 있는 방법이 최소 1가지 이상 존재한다.
따라서 a에 1이 없다면, 어떤 상한이 반드시 존재하므로, 그 이상의 수는 무조건 만들 수 있다는 뜻이다.
5. 슈어의 상한(Schur's bound)
대략적인 상한을 어떻게 구할까
여러가지가 있긴함
1) $a_{1},a_{2},...,a_{n}$에서 min값과 max값에 대하여, 프로베니우스의 수 g(a_{1},...a_{n})의 상한은

2) n개의 동전의 max값에 대하여, 프로베니우스의 수의 상한은...

3) n개의 동전에 대하여..

6. 예시 문제
https://www.acmicpc.net/problem/35293
+9, +7, +4.5, -2를 가지고 정확히 n을 만들기 위해 각각 사용하는 횟수들의 합은 얼마인가?
여기서 n의 최댓값이 $10^{16}$이다.
n이 매우 크기 때문에, n을 어떤 방법으로든 줄이는게 해법임은 쉽게 눈치챌 수 있는데.. 어떻게 줄일 수 있을까?
먼저 n이 소수점 한자리까지만 주어지는데, n이 매우 크다면(10^15이상), float(input())으로 받더라도, 정수로 처리해버린다고 한다.
그래서 소수점 한자리까지만 주어진다는 점을 이용해서 input()으로 받은 다음, .을 기준으로 정수부분과 소수부분을 나눠야한다.
n = input()
f = n[-1]
m = n[:-2]
4.5만 사용할 수 있기 때문에 주어진 값이 5.0, 7.5같이 ~.0이나 ~.5가 아니면 절대로 불가능하다.
n = input()
f = n[-1]
m = n[:-2]
if f == 0 or f == 5:
else:
print(-1)
4.5를 다루기는 까다롭기 때문에, 각각의 수를 2배한다.
즉, 9,4.5,7,-2를 2배하여 18,14,9,-4를 사용한다고 가정한다.
당연히 n도 2배를 해야한다.
n = input()
f = int(n[-1])
m = int(n[:-2])
if f == 0 or f == 5:
m *= 2
if f == 5:
m += 1
else:
print(-1)
이제 18원, 14원, 9원, -4원을 사용하여 n원을 만드는 방법에 대해 생각해보자.
이 문제는 18x + 14y + 9z -4w = n을 만족하는 x+y+z+w의 최솟값을 구하는 문제와 같다.
n이 적당히 작다면, BFS를 이용해서 쉽게 구할 수 있다.
다만, n이 매우 크다면 어떨까?
먼저 18을 사용하지 않는다고 가정해보자. 즉, 14,9,-4만으로 매우 큰 n을 만들 수 있을까?
+9를 2번 선택하면, +18을 1번 선택하는 것과 마찬가지이므로, x+y+z+w의 최솟값을 구하기 위해서는
+9를 2번 선택하는 것보다 +18을 1번 선택하는 것이 유리하다.
마찬가지로 +14를 9번 쓰는 것보다 +18을 7번 쓰는 것이 유리하다. 따라서, 18은 사용하는 것이 무조건 유리하다.
그렇다면, 18,14,9,-4를 사용한다고 생각해보자.
하지만, 18 1번, -4 1번을 사용하는 것보다 14 1번을 사용하는 것이 무조건 유리하다.
그러므로, 적당히 큰 n에 대해서는 -4는 사용할 필요가 없다. 왜냐하면 18을 무조건 써야하는데, -4까지 써야한다면 그것은 14로 대체되기 때문이다.
따라서, 매우 큰 n에 대해서는 18,14,9를 사용하여 n을 만드는 문제와 같다.
그런데, 18,14,9의 최대공약수는 1이므로, 18,14,9에 대한 프로베니우스의 수가 존재한다.
즉, 프로베니우스의 수보다 큰 수는 18,14,9로 무조건 만들 수 있다.
그러므로, n이 매우 크다면, (18x+14y+9z) + (18a+14b+9c-4w) = n으로 분해해서 생각할 수 있다.
여기서 (18x+14y+9z)는 매우 큰 수이고, (18a+14b+9c-4w)는 적당한 프로베니우스의 수 이하의 수이다.
그러면 어떻게 분해하는게 유리할까?
매우 큰 수 (18x+14y+9z)는 y = 0, z = 0이고 x만을 사용하는게 유리하다.
즉, 매우 큰 n에 대해서 일단 18을 사용해서 최대한 줄이고, 적당히 작은 수로 만든 다음, 그 수에 대해서는 18a+14b+9c-4w로
BFS를 이용해 a+b+c+w의 최솟값을 구하는 것이다.
여기서 문제는 18을 사용해서 최대한 줄일때, 어디까지 줄이냐이다.
슈어의 상한을 이용해서 18*9 - 18 - 9 = 162-27 = 135정도인데, 적당히 안전하게 n이 200이하에 대해서는 BFS로 가능한 모든 경우를 구해 DP배열에 저장해두고,
N이 매우 큰 경우, 일단 18만 사용해서 N을 200근처로 떨어뜨린 다음, 미리 구해둔 DP배열의 수를 사용해서 그 수와 더하면 된다
from collections import deque
n = input()
f = int(n[-1])
m = int(n[:-2])
if f == 0 or f == 5:
m *= 2
if f == 5:
m += 1
INF = 10**12
dp = [INF]*201
dp[0] = 0
dp[18] = 1
dp[9] = 1
dp[14] = 1
queue = deque([18,14,9])
while queue:
u = queue.popleft()
for a in [18,14,9,-4]:
du = u + a
if du >= 0 and du <= 200:
if dp[du] > dp[u]+1:
dp[du] = dp[u]+1
queue.append(du)
count = 0
if m > 200:
count += (m-183)//18
m -= count*18
print(count + dp[m])
else:
print(-1)
어떤 수 m을 200 근방으로 떨어뜨리는 스킬은 (m-183)//18이다.
(m-x)//d하면 m을 x~x+(d-1)사이로 만들 수 있다.
왜냐하면, m-x = dq + r이다.
이떄, 남은 숫자는 m - dq이므로, m-dq = x+r이다.
r는 0~d-1이므로, 남은 숫자 m-dq는 x~x+(d-1)까지이다.
'정수론' 카테고리의 다른 글
| Prime number Theorem으로 알아보는 구간 내의 소수 개수 (0) | 2025.07.02 |
|---|---|
| 에라토스테네스의 체 변형 segmented sieve 배우기 (0) | 2025.06.30 |
| 의외로 모르는 어떤 분수가 유한소수가 될 수 있는 조건 (0) | 2025.04.29 |
| 모든 양의 유리수는 단위분수의 합으로 나타낼 수 있다 (0) | 2025.04.28 |
| 정수의 임의의 거듭제곱들의 합을 바라보는 놀라운 방법 (0) | 2025.02.11 |