- 수학 논리 퀴즈와 같은 부류의 또 다른 문제이다

- 파이썬 풀이 부분에서 수학 논리 퀴즈에서 사용한 코드를 참고했다

- 유명한 문제이다: https://en.wikipedia.org/wiki/Sum_and_Product_Puzzle

- 위 링크에선 $x < y$인 조건이 있는데 여기선 무시했다

문제 상황

- 2 이상 100 이하의 두 수 $x, y$를 고려하자

- A에게는 두 수의 곱을 B에게는 두 수의 합을 알려주었다

- 아래 A와 B의 대화를 바탕으로 $x, y$를 맞혀라

- A: 도저히 모르겠는데요

- B: 당신이 모를 거란 것쯤은 이미 알고 있었어요

- A: 아, 두 수가 뭔지 알겠습니다

- B: 저도 두 수가 뭔지 알겠습니다

풀이

A: 도저히 모르겠는데요

- 두 수의 곱을 $P$라고 하자

- A가 정답을 모르겠다는 것은 $P=xy$를 만족하는 $(x,y)$ 쌍이 2개 이상임을 뜻한다

B: 당신이 모를 거란 것쯤은 이미 알고 있었어요

- 두 수의 합을 $S$라고 하자

- 일단 $S$는 6 이상 198 이하이다

- 그렇지 않다면 B는 A의 답변을 듣지 않고도 정답을 바로 맞힐 수 있다

- $S=x+y$를 만족하는 $(x,y)$쌍을 찾아보자

- $S$가 홀수일 때 두 수 $x,y$로 가능한 조합은 $(2, S-2), \cdots, \left(\big\lfloor\frac{S}{2}\big\rfloor, \big\lfloor\frac{S}{2}\big\rfloor + 1 \right)$이고

- $S$가 짝수일 때 두 수 $x,y$로 가능한 조합은 $(2, S-2), \cdots, \left(\frac{S}{2}, \frac{S}{2}\right)$이다

- 그런데 B는 A가 정답을 모를거라고 확신했다

- 가능한 모든 $(x,y)$쌍에 대해 $xy$를 계산해보자

- 만약 $xy$ 중에 두 소수의 곱이 존재한다면 B는 A가 정답을 알지 못한다고 확신할 수 없다

- 왜냐하면 두 수의 곱이 $P$인 두 수 $(x, y)$ 쌍은 1개만 존재하기 때문이다

- 예컨대 A가 $P=21$을 알고 있다면 두 수는 3과 7뿐이므로 바로 정답을 맞힐 수 있다

- 그런데 어떻게 $S=x+y$를 만족하는 소수 $x,y$가 존재하는지 판단할 수 있을까?

- 쉬운 방법으론 다음이 있다. $S=x+y$를 만족하는 가능한 모든 $(x,y)$ 쌍을 고려하자

- 그리고 $x$와 $y$의 곱을 $P$라고 하자

- 이때 $P=ab$를 만족하는 2 이상 100 이하의 $(a,b)$ 쌍이 단 1개라면 $x,y$는 소수이다

- 즉, B는 당신이 모를 거란 것쯤은 이미 알고 있었어요와 같이 대답할 수 없으므로 모순이다

  • 아래는 탐색 시간을 줄이는 알고리즘이다

- 일단 임의의 짝수 $S = x + y$에 대해선 소수 $x,y$가 항상 존재한다

- 여기에는 골드바흐의 추측이 사용된다 (한 번쯤 들어봤을 법하다)

- 밀레니엄 7대 난제 중 하나인 골드바흐의 추측은 $4$이상의 모든 짝수는 두 소수의 합으로 표현 가능함을 뜻한다

- 물론 완전히 증명된건 아니지만 충분히 큰 영역까지 성립함이 보여졌다

- 따라서 이 문제에서 다루는 영역은 당연히 성립한다

- 그럼 $S$가 홀수일 땐 괜찮을까?

- $S$가 홀수일려면 필연적으로 $x,y$ 중 하나는 짝수, 다른 하나는 홀수여야 한다

- 둘 다 짝수이거나 홀수라면 $x+y$는 짝수이기 때문에 그렇다

- $x$와 $y$는 서로 대칭적이므로 임의로 $x$를 짝수 $y$를 홀수라 가정하겠다

- $x$가 만약 2이고 $y$가 소수라면 $xy$는 2와 y의 곱으로만 이루어진다

- 하지만 $x$가 4이상의 짝수라면 $xy$는 합성수이다

- 그런데 이는 상한 100을 고려하지 않은 것으로 $x$가 4이상의 짝수라도 $y$가 50이상의 소수라면 $xy$는 유일한 해 1개를 가진다 (곱의 쌍이 단 1개)

- 그런데 $x$가 4 이상의 짝수이고 $y$가 소수가 아닌 홀수여도 하한과 상한 조건에 의해 $xy$를 나타내는 곱의 쌍이 1개만 존재할 수 있을까?

- $x$가 98, $y$가 95이면 가능하다

- 9310은 95와 98의 곱으로만 가능하다

- 짝수인 $S$는 두 소수의 합으로 표현되므로 정답에서 제외하고 홀수인 $S$에 대해선 브루트 포스로 정답 여부를 판단하자

- 즉, 홀수 $S(=x+y)$를 만족하는 $(x,y)$ 쌍에 대해 $xy$가 $x$와 $y$의 곱으로만 표현된다면 정답이 될 수 없다

- 이 과정을 거쳐 정답으로 가능한 두 수의 합을 $S^*$라고 정의하겠다

A: 아, 두 수가 뭔지 알겠습니다

- 기본적으로 곱의 쌍이 1개뿐인 수는 배제한다 (첫 단계에서 A가 정답을 맞혔을 것이다)

- $P=xy$를 만족하는 모든 $(x,y)$ 쌍에 대해 다음을 계산하자

- $S=x+y$라 하고 $S=a+b$을 만족시키는 모든 $(a,b)$에 대해 $ab$을 계산하자

- 만약 $ab$중 곱의 쌍이 1개뿐인 수가 존재한다면 B는 A가 모를거라고 확신할 수 없다

- 즉, 해당 $x,y$는 정답이 아닌 것이다

- 그런데 위를 만족하는 $(x,y)$ 조합이 단 1개뿐이라면 A는 두 수가 뭔지 안다

- 예컨대 A가 처음에 전달 받은 $P$가 18이라고 해보자

- 그럼 $x,y$는 2와 9 또는 3과 6이다

- 따라서 A는 도저히 알 수 없다

- 그런데 B는 A가 모를걸 알고 있었다

- 곱이 18이므로 합은 9 아니면 11이다

- 만약 두 수의 합 $S$가 $9$라면 $2,7$이 가능하다

- 그러면 두 수의 곱은 14가 되는데 14는 오직 2와 7의 곱으로만 이루어진다

- 따라서 B당신이 모를 거란 것쯤은 이미 알고 있었어요라고 생각할 수 없다

- 이제 두 수의 합이 11이라고 해보자

- 두 수의 합이 11인 쌍은 $(2,9), (3,8),(4,7),(5,6)$이다

- 각 쌍의 원소 곱은 $18, 24, 28, 30$이다

- 이들은 모두 합성수 이므로 A는 정답을 알지 못한다

- 즉, B당신이 모를 거란 것쯤은 이미 알고 있었어요라고 생각할 수 있다

- 따라서 A가 전달 받은 두 수의 곱이 18이라면 가능한 합은 11뿐이고 이 때의 $x,y$는 2와 9이다

- 경우의 수가 1개뿐이므로 A는 두 수가 무엇인지 알 수 있다

B: 저도 두 수가 뭔지 알겠습니다

- A가 아, 두 수가 뭔지 알겠습니다라고 말한 정보를 이용해보자

- 일단 B는 당신이 모를 거란 것쯤은 이미 알고 있었어요라고 말했으므로 두 수의 합으로 가능한 것들은 $S^*$이다

- $S^* = x+y$를 만족하는 $(x, y)$에 대해 $xy$를 계산하면 $xy=ab$를 만족하는 $(a,b)$쌍은 2개 이상이다 (만약 1개뿐이라면 $S^*$일 수 없다)

- 가능한 $(a,b)$쌍 각각에 대해 $a+b$가 $S^*$에 포함되는지 확인하자

- 만약 $a+b$가 $S^*$에 포함되지 않는다면 해당 $(a,b)$는 정답이 될 수 없다 (B가 당신이 모를 거란 것쯤은 이미 알고 있었어요라고 말할 수 없다)

- 가능한 $(a,b)$쌍 중에서 $S^*$에 포함되는 $(a,b)$쌍이 단 1개뿐이라면 A는 아, 두 수가 뭔지 알겠습니다라고 말할 수 있다

- 이 과정을 가능한 모든 $(x,y)$쌍에 적용한 후 단 하나의 $(x,y)$쌍에 대해서만 A가 아, 두 수가 뭔지 알겠습니다라고 말할 수 있다면

- B는 저도 두 수가 뭔지 알겠습니다라고 말할 수 있다

파이썬으로 풀어보자

A: 도저히 모르겠네요

- 로직을 정리하고 가자

- 일단 두 수의 곱 $P$가 주어지면 $P = xy$를 만족하는 $(x,y)$ 쌍을 찾는다

- 단, $\min \leq x \leq y \leq \max$ (여기서 $\min=2, \max=100$)

- 곱의 조합 쌍이 2개 이상이면 A는 두 수가 무엇인지 알 수 없다

  • 곱의 조합 쌍을 찾자
import math


def find_product_pairs(n, minimum, maximum):
    product_pairs = []
    for x in range(minimum, math.isqrt(n) + 1):
        if n % x != 0:
            continue
        y = n // x
        if not (x <= y and minimum <= x <= maximum and minimum <= y <= maximum):
            continue
        product_pairs.append((x, y))
    return product_pairs


# 예시
n = 42
minimum = 2
maximum = 100
product_pairs = find_product_pairs(n, minimum, maximum)
print(product_pairs)
[(2, 21), (3, 14), (6, 7)]
  • 곱의 조합쌍이 2개 이상이면 A는 정답을 맞힐 수 없다
def has_elements(sequence):
    return len(sequence) >= 2


def step1(n, minimum, maximum):
    return find_product_pairs(n, minimum, maximum)


def check_step1(pairs):
    """A가 모르겠다고 한 것이 타당한지 확인"""
    return has_elements(pairs)


# 예시
n = 20
minimum = 2
maximum = 100
pairs = step1(n, minimum, maximum)
result = check_step1(pairs)
answer = "압니다" * (1 - result) + "모릅니다" * result
print(f"두 수의 곱으로 {n}이(가) 주어졌을 때 A는 두 수가 무엇인지 압니까?: {answer}")
print(f"현재 A가 정답으로 고려하고 있는 후보군: {pairs}")
두 수의 곱으로 20이(가) 주어졌을 때 A는 두 수가 무엇인지 압니까?: 모릅니다
현재 A가 정답으로 고려하고 있는 후보군: [(2, 10), (4, 5)]

B: 당신이 모를 거란 것은 이미 알고 있었어요

- 짝수 $S$는 골드바흐의 추측에 의해 정답이 될 수 없다

- 홀수 $S=x+y$를 만족하는 가능한 모든 $(x,y)$ 쌍을 고려하자

- 모든 $(x,y)$쌍에 대해 $xy$를 계산하고 $xy$중에 곱의 쌍이 1개 뿐인 $xy$가 존재한다면

- 해당 $S$는 처음에 B가 받은 수가 될 수 없다

def use_goldbach_conjecture(num):
    if num <= 2:
        return False
    return is_even(num)


def is_even(num):
    return num % 2 == 0


def find_sum_pairs(n, minimum, maximum):
    sum_pairs = []
    for x in range(minimum, maximum + 1):
        y = n - x
        if not (minimum <= y <= maximum and x <= y):
            continue
        sum_pairs.append((x, y))
    return sum_pairs


def filter_sum_pairs_step2(sum_pairs, minimum, maximum):
    selected_pairs = []
    for x, y in sum_pairs:
        n = x * y
        pairs = step1(n, minimum, maximum)
        if not check_step1(pairs):
            continue
        selected_pairs.append((x, y))
    return selected_pairs


def calculate_s_star(minimum, maximum):
    s_star_set = set()
    possible_summations = range(minimum * 2, maximum * 2 + 1)
    for sum_ in possible_summations:
        if use_goldbach_conjecture(sum_):
            continue
        sum_pairs = find_sum_pairs(sum_, minimum, maximum)
        selected_pairs = filter_sum_pairs_step2(sum_pairs, minimum, maximum)
        if len(sum_pairs) != len(selected_pairs):  # B는 A의 답변을 듣기 전부터 A가 정답을 맞히지 못 할 것을 알고 있었다
            continue
        s_star_set.add(sum_)
    return s_star_set


def check_step2(num):
    """B가 `A는 정답을 못 맞힐 거라고` 말한 것이 타당한지 확인"""
    return num in S_STAR
minimum = 2
maximum = 100
S_STAR = calculate_s_star(minimum, maximum)
S_STAR
{11, 17, 23, 27, 29, 35, 37, 41, 47, 53}

A: 아, 두 수가 뭔지 알았습니다

- 주어진 곱을 $(x, y)$ 쌍으로 나눈다

- 각 곱에 대해 A: 도저히 모르겠네요에서 사용한 알고리즘을 적용한다 (15, 21과 같은 수를 스킵하기 위함이다)

- 남은 곱에 대해 합을 구한다

- 각 합에 대해 B: 당신이 모를 거란 것은 이미 알고 있었어요에서 사용한 알고리즘을 적용한다

- 합이 $S^*$에 속해야 한다, 그렇지 않으면 제외한다

- 하지만 A는 두 수가 무엇인지 알아냈으므로 이를 만족하는 곱의 쌍이 단 1개 뿐인 것을 의미한다

  • 주어진 곱을 $(x, y)$ 쌍으로 나누자

  • 각 쌍에 대해 step1 함수의 알고리즘를 적용하고 남은 쌍에 대해 합을 구하자

  • 각 합에 대해 $S^*$에 속하는지 계산하자

  • $S^*$에 속하는 합이 1개만 남았다면 A는 정답을 맞힐 수 있다

def filter_product_pairs_step3(product_pairs, minimum, maximum):
    selected_pairs = []
    for x, y in product_pairs:
        s = x + y
        if not check_step2(s):
            continue
        selected_pairs.append((x, y))
    return selected_pairs


def step3(n, minimum, maximum):
    pairs = step1(n, minimum, maximum)
    if not check_step1(pairs):
        return []
    selected_pairs = filter_product_pairs_step3(pairs, minimum, maximum)
    return selected_pairs


def check_step3(pairs):
    """A가 두 수가 무엇인지 알겠다고 한 것이 타당한지 확인"""
    return len(pairs) == 1


# 예시
n = 52
minimum = 2
maximum = 100
pairs = step3(n, minimum, maximum)
result = check_step3(pairs)
answer = "압니다" * result + "모릅니다" * (1 - result)
print(f"두 수의 곱으로 {n}이(가) 주어졌을 때 B의 답변을 들은 A는 두 수가 무엇인지 압니까?: {answer}")
print(f"현재 A가 정답으로 고려하고 있는 후보군: {pairs}")
두 수의 곱으로 52이(가) 주어졌을 때 B의 답변을 들은 A는 두 수가 무엇인지 압니까?: 압니다
현재 A가 정답으로 고려하고 있는 후보군: [(4, 13)]

- A: 아, 두 수가 뭔지 알았습니다 상황까지 모순이 없음을 가정한다

- 즉, 모든 step 함수에 대해 이전 step 함수까지 모순이 없음을 가정한다

n = 15
minimum = 2
maximum = 100
pairs = step3(n, minimum, maximum)
result = check_step3(pairs)
answer = "압니다" * result + "모릅니다" * (1 - result)
print(f"두 수의 곱으로 {n}이(가) 주어졌을 때 B의 답변을 들은 A는 두 수가 무엇인지 압니까?: {answer}")
print(f"현재 A가 정답으로 고려하고 있는 후보군: {pairs}")
두 수의 곱으로 15이(가) 주어졌을 때 B의 답변을 들은 A는 두 수가 무엇인지 압니까?: 모릅니다
현재 A가 정답으로 고려하고 있는 후보군: []

- 두 수의 곱이 15라면 사실 A는 정답을 안다 (두 수는 3과 5이다)

- 하지만 이는 A: 아, 두 수가 뭔지 알았습니다 단계가 아닌 최초에 알았어야 하므로 모른다로 답변하게 설계했다

B: 저도 두 수가 뭔지 알겠습니다

- 주어진 합을 $(x, y)$ 쌍으로 나누고 곱을 구한다

- 이제 각 곱에 대해 생각해보자

- 각 곱에 대해 A: 아, 두 수가 뭔지 알았습니다에서 사용한 알고리즘을 적용한다

- 남은 곱이 단 하나뿐이라면 B는 정답을 맞힐 수 있다

- 쉽게 말해 각 곱에 대해 다음을 생각하자

- 내가 A라면 A: 아, 두 수가 뭔지 알았습니다라고 말하며 정답을 맞힐 수 있었을까?

- 그렇다면 통과시키고 아니라면 제외한다

- 남은 곱이 1개뿐이면 B도 정답을 맞힐 수 있다

def filter_sum_pairs_step4(sum_pairs, minimum, maximum):
    selected_pairs = []
    for x, y in sum_pairs:
        n = x * y
        pair = step3(n, minimum, maximum)
        if not check_step3(pair):
            continue
        selected_pairs.append((x, y))
    return selected_pairs


def step4(n, minimum, maximum):
    if not check_step2(n):
        return []
    sum_pairs = find_sum_pairs(n, minimum, maximum)
    selected_pairs = filter_sum_pairs_step4(sum_pairs, minimum, maximum)
    return selected_pairs
    
    
def check_step4(pairs):
    """B가 두 수가 무엇인지 알겠다고 한 것이 타당한지 확인"""
    return len(pairs) == 1


# 예시
n = 41
minimum = 2
maximum = 100
pairs = step4(n, minimum, maximum)
result = check_step4(pairs)
answer = "압니다" * result + "모릅니다" * (1 - result)
print(f"두 수의 합으로 {n}이(가) 주어졌을 때 A의 답변을 들은 B는 두 수가 무엇인지 압니까?: {answer}")
print(f"현재 B가 정답으로 고려하고 있는 후보군: {pairs}")
두 수의 합으로 41이(가) 주어졌을 때 A의 답변을 들은 B는 두 수가 무엇인지 압니까?: 모릅니다
현재 B가 정답으로 고려하고 있는 후보군: [(3, 38), (4, 37), (7, 34), (9, 32), (10, 31), (12, 29), (13, 28), (14, 27), (15, 26), (16, 25), (17, 24), (18, 23), (19, 22)]
def solve(minimum, maximum, log=True):
    if log:
        print("#### 두 수의 합과 숫자 쌍 ####")
    answer = []
    for n in range(minimum * 2, maximum * 2 + 1):
        pairs = step4(n, minimum, maximum)
        if log:
            print(f"{n}: {pairs}")
        if not check_step4(pairs):
            continue
        pair = pairs[0]
        answer.append(pair)
    return answer
minimum = 2
maximum = 100
S_STAR = calculate_s_star(minimum, maximum)
solution = solve(minimum, maximum)
#### 두 수의 합과 숫자 쌍 ####
4: []
5: []
6: []
7: []
8: []
9: []
10: []
11: [(2, 9), (3, 8), (4, 7)]
12: []
13: []
14: []
15: []
16: []
17: [(4, 13)]
18: []
19: []
20: []
21: []
22: []
23: [(4, 19), (7, 16), (10, 13)]
24: []
25: []
26: []
27: [(2, 25), (4, 23), (5, 22), (7, 20), (8, 19), (9, 18), (10, 17), (11, 16), (13, 14)]
28: []
29: [(2, 27), (4, 25), (6, 23), (7, 22), (8, 21), (10, 19), (11, 18), (12, 17), (13, 16)]
30: []
31: []
32: []
33: []
34: []
35: [(3, 32), (4, 31), (6, 29), (8, 27), (9, 26), (10, 25), (12, 23), (14, 21), (16, 19), (17, 18)]
36: []
37: [(5, 32), (6, 31), (8, 29), (9, 28), (10, 27), (16, 21), (17, 20)]
38: []
39: []
40: []
41: [(3, 38), (4, 37), (7, 34), (9, 32), (10, 31), (12, 29), (13, 28), (14, 27), (15, 26), (16, 25), (17, 24), (18, 23), (19, 22)]
42: []
43: []
44: []
45: []
46: []
47: [(4, 43), (6, 41), (7, 40), (10, 37), (13, 34), (15, 32), (16, 31), (17, 30), (18, 29), (19, 28), (20, 27), (22, 25), (23, 24)]
48: []
49: []
50: []
51: []
52: []
53: [(5, 48), (6, 47), (8, 45), (10, 43), (12, 41), (13, 40), (15, 38), (16, 37), (17, 36), (18, 35), (19, 34), (20, 33), (21, 32), (22, 31), (23, 30), (24, 29), (25, 28), (26, 27)]
54: []
55: []
56: []
57: []
58: []
59: []
60: []
61: []
62: []
63: []
64: []
65: []
66: []
67: []
68: []
69: []
70: []
71: []
72: []
73: []
74: []
75: []
76: []
77: []
78: []
79: []
80: []
81: []
82: []
83: []
84: []
85: []
86: []
87: []
88: []
89: []
90: []
91: []
92: []
93: []
94: []
95: []
96: []
97: []
98: []
99: []
100: []
101: []
102: []
103: []
104: []
105: []
106: []
107: []
108: []
109: []
110: []
111: []
112: []
113: []
114: []
115: []
116: []
117: []
118: []
119: []
120: []
121: []
122: []
123: []
124: []
125: []
126: []
127: []
128: []
129: []
130: []
131: []
132: []
133: []
134: []
135: []
136: []
137: []
138: []
139: []
140: []
141: []
142: []
143: []
144: []
145: []
146: []
147: []
148: []
149: []
150: []
151: []
152: []
153: []
154: []
155: []
156: []
157: []
158: []
159: []
160: []
161: []
162: []
163: []
164: []
165: []
166: []
167: []
168: []
169: []
170: []
171: []
172: []
173: []
174: []
175: []
176: []
177: []
178: []
179: []
180: []
181: []
182: []
183: []
184: []
185: []
186: []
187: []
188: []
189: []
190: []
191: []
192: []
193: []
194: []
195: []
196: []
197: []
198: []
199: []
200: []
print("문제의 정답:", solution)
문제의 정답: [(4, 13)]

- 빈 리스트는 중간에 모순이 발견되어 B: 저도 두 수가 뭔지 알겠습니다까지 도달하지 못한 것을 의미한다

- 11과 같이 숫자 쌍이 2개 이상 존재하는 수는 B: 저도 두 수가 뭔지 알겠습니다에 모순되는 것을 의미한다 (B는 답이 어느 것인지 알지 못한다)

- 17처럼 숫자 쌍이 하나만 존재하는 것은 B: 저도 두 수가 뭔지 알겠습니다를 포함한 모든 대화가 모순 없이 이루어졌음을 뜻한다 (즉, 문제의 정답을 의미한다)

- 따라서 문제의 정답은 $(4,13)$이다