Задача о двух мудрецах уже много лет всплывает на различных форумах и постоянно возобновляет к себе интерес. Напомню условие:
У некоторого султана было два мудреца: Али-ибн-Вали и Вали-ибн-Али. Желая убедиться в их мудрости, султан призвал мудрецов к себе и сказал: «Я задумал два числа. Оба они целые, каждое больше единицы, но меньше ста. Я перемножил эти числа и результат сообщу Али и при этом Вали я скажу сумму этих чисел. Если вы и вправду так мудры, как о вас говорят, то сможете узнать исходные числа».
Султан сказал Али произведение, а Вали – сумму. Мудрецы задумались. Первым нарушил молчание Али.
— Я не знаю этих чисел, — сказал он, опуская голову.
— Я это знал, — подал голос Вали.
— Тогда я знаю эти числа, — обрадовался Али.
— Тогда и я знаю! — воскликнул Вали.
И мудрецы сообщили пораженному султану задуманные им числа.
Назовите эти числа.

Готовой компьютерной программы, позволяющей решать такие задачи при любом заданном максимальном числе, обнаружить не удалось. Поэтому я решил сам написать подобную программу. Алгоритм решения буду описывать на примере классической задачи для максимального числа, равного 100.

1. Я не знаю этих чисел, сказал мудрец, знающий произведение двух чисел


Отсюда вытекает, что это произведение, не может быть однозначно представлено в виде произведения двух чисел. Есть как минимум несколько способов, как можно получить это произведение с другими парами чисел, причем эти числа должны удовлетворять условию, что они оба меньше 100. Произведение, которое можно представить только одним способом, будем в дальнейшем называть уникальным.

С этого высказывания можно сделать вывод о некоторых свойствах этих чисел. Во-первых, они не могут оба быть одновременно простыми, иначе их произведение уникально и представляется только в виде произведения этих двух простых чисел. Это условие является необходимым, но не достаточным условием уникальности произведения. В дальнейшем мы обнаружим другие уникальные произведения, которые не являются произведением двух простых чисел.

2. Я это знал, сказал мудрец, знающий сумму двух чисел


Сумму двух чисел S, можно представить парой чисел различными способами: 2 + (S — 2), 3 + (S — 3) и т.д.

Это высказывание говорит нам, что ни одна из этих пар чисел, при умножении друг на друга не дает уникальное произведение. Попробуем определить какие суммы двух чисел имеют такие свойства, т.е. составим список возможных кандидатов для сумм двух чисел.

Во-первых, как мы уже показали, два числа не могут быть оба простыми, поэтому их сумма не может быть суммой двух простых чисел. Итак, компьютерная программа, сначала вычисляет список всех простых чисел, которые меньше заданного максимального числа. Далее, находит все возможные суммы, которые могут быть получены этими простыми числами и исключает их из дальнейшего исследования. В результате мы получим первое приближение — возможных сумм:

11 17 23 27 29 35 37 41 47 51 53 57 59 65 67 71 77 79 83 87 89 93 95 97 101 103 105 107 109 111 113 115 117 119 121 123 125 127 129 131 133 135 137 139 141 143 145 147 149 151 153 155 157 159 161 163 165 167 169 171 173 174 175 177 179 181 182 183 184 185 187 188 189 190 191 192 193 195 196

В принципе, мы могли бы сразу все эти возможные суммы проверять на уникальность. Т.е. для каждой суммы составляем все возможные пары чисел и произведение этих чисел проверяем на уникальность. Если хоть одна пара чисел, для исследуемой суммы, дает уникальное произведение, то эта сумма вычеркивается из возможных кандидатов. Эта подпрограмма для проверки на уникальность произведения — самая сложная часть всей программы. Многие, кто пытался составить подобную программу, просто забывали об этом и получали не до конца корректные результаты.

В этой подпрограмме в начале исследуемое произведение разбивается на простые множители, далее составляется список всех возможных произведений, которые могут быть получены их этих простых множителей — это первый множитель. Второй множитель получаем делением произведения на первый множитель и проверяем только варианты, когда первый множитель меньше второго. Далее оба множителя проверяем, удовлетворяют ли они условию задачи (оба меньше максимального числа 100, или их сумма меньше Max). Так мы получаем допустимые разложения произведения на два множителя и если таких разложений только одно, то это произведение уникально.

Но перед тем, как начать проверку на уникальность, мы упростим задачу, чтобы не грузить компьютер лишними вычислениями. Воспользуемся не очень очевидным свойством — какая сумма может быть максимальной.
Если исследуемое произведение имеет множителем простое число больше 50 (больше половины Мax, в нашем случае это простое число 53), то это произведение уникально. Так как при попытке получить второй вариант разложения, мы как минимум должны умножать 53 на 2 и получаем множитель больший 100.

Для любой суммы S, больше чем 53 + 2, мы можем найти пару чисел 53 и S — 53 и произведение этих чисел будет уникальным. Отсюда делаем вывод, что все суммы больше чем 55 можно исключить из дальнейших вычислений.
Это сужает круг поиска до 11 возможных сумм:

11 17 23 27 29 35 37 41 47 51 53

Теперь делаем проверку на уникальность произведения всех возможных пар чисел. Программа выводит уникальные произведения с отрицательным знаком.

1: X + Y = 11, X * Y =: 18 24 28 30
2: X + Y = 17, X * Y =: 30 42 52 60 66 70 72
3: X + Y = 23, X * Y =: 42 60 76 90 102 112 120 126 130 132
4: X + Y = 27, X * Y =: 50 72 92 110 126 140 152 162 170 176 180 182
5: X + Y = 29, X * Y =: 54 78 100 120 138 154 168 180 190 198 204 208 210
6: X + Y = 35, X * Y =: 66 96 124 150 174 196 216 234 250 264 276 286 294 300 304 306
7: X + Y = 37, X * Y =: 70 102 132 160 186 210 232 252 270 286 300 312 322 330 336 340 342
8: X + Y = 41, X * Y =: 78 114 148 180 210 238 264 288 310 330 348 364 378 390 400 408 414 418 420
9: X + Y = 47, X * Y =: 90 132 172 210 246 280 312 342 370 396 420 442 462 480 496 510 522 532 540 546 550 552
10: X + Y = 51, X * Y =: 98 144 188 230 270 308 344 378 410 440 468 494 518 540 560 -578 594 608 620 630 638 644 648 650
11: X + Y = 53, X * Y =: 102 150 196 240 282 322 360 396 430 462 492 520 546 570 592 612 630 646 660 672 682 690 696 700 702

Обнаруживается одно уникальное произведение — 578 = 2*17*17. Действительно это число можно представить только одним способом — 17 * 34, второй способ 2 * 289, не удовлетворяет условию — оба числа меньше 100.

Значит сумма 51 так же удаляется их возможных кандидатов, оставляя только 10 допустимых сумм.

Кстати, благодаря программе, можно легко найти следующее простое правило, как обнаружить уникальные произведения.

Если произведение имеет вид 2 * P * P, где P — простое число, квадрат которого больше максимального числа (100), то это произведение уникально и оно соответствует сумме двух чисел P и 2*P и эта сумма равна 3*P.

Значит мы можем вычеркивать все суммы, которые равны 3*P, где P простое число больше 10. В нашем случае это сумма 51 = 3 * 17.

При дальнейшей работе с программой обнаружились еще и другие интересные правила для уникальных произведений…

Итак, после первых двух реплик мудрецов, первый мудрец знает, что сумма двух чисел может быть только одной из 10 чисел:

11 17 23 27 29 35 37 41 47 53

3. Тогда я знаю эти числа, — обрадовался Али


В каком случае Али может однозначно определить, на какую пару чисел разложить свое произведение? На ту пару чисел, сумма которой, равна одной из этих 10 сумм, причем только одна пара чисел имеет сумму из этого множества. С точки зрения компьютерного алгоритма, те произведения, которые встречаются больше одного раза, должны быть удалены. Например, произведение 30 встречается два раза — для суммы 11 и для суммы 17. Если бы Али сказали число 30, то он бы обнаружил, что условиям задачи удовлетворяют две пары чисел: 5, 6 (сумма 11) и 2, 15 (сумма 17) и он не мог бы однозначно сказать — я знаю эти числа. Значит все повторяющиеся произведения удаляются. В результате мы получаем такую картину — все допустимые суммы и все допустимые произведения для этих сумм:

1: X + Y = 11, X * Y =: 18 24 28
2: X + Y = 17, X * Y =: 52
3: X + Y = 23, X * Y =: 76 112 130
4: X + Y = 27, X * Y =: 50 92 110 140 152 162 170 176 182
5: X + Y = 29, X * Y =: 54 100 138 154 168 190 198 204 208
6: X + Y = 35, X * Y =: 96 124 174 216 234 250 276 294 304 306
7: X + Y = 37, X * Y =: 160 186 232 252 270 336 340
8: X + Y = 41, X * Y =: 114 148 238 288 310 348 364 378 390 400 408 414 418
9: X + Y = 47, X * Y =: 172 246 280 370 442 480 496 510 522 532 540 550 552
10: X + Y = 53, X * Y =: 240 282 360 430 492 520 570 592 612 630 646 660 672 682 690 696 700 702

4. Тогда и я знаю! — воскликнул Вали


После третьей реплики, Вали проделал всю вычислительную работу как и мы и отбросил все повторяющиеся произведения. И он может определить однозначно числа только тогда, когда для его суммы, остается допустимым только одно произведение. С точки зрения компьютера — в одной строчке осталось только одно допустимое произведение. В нашем случае — это произведение 52 для суммы 17, которое соответствует паре чисел 4, 13. Это решение является единственным.

Моя компьютерная программа позволяет получить результат задачи для любого значения максимального числа.
Существуют различные вариации этой задачи:
— оба числа меньше заданного;
— сумма чисел меньше заданного максимального числа;
— числа могут быть одинаковые;
— числа обязательно разные;
Программа позволяет рассчитать результат для всех этих вариаций.
Программа может выводить промежуточные результаты для каждого этапа вычислений.
Также есть возможность вычислить все граничные точки, при которых появляются новые решения для указанного диапазона чисел. Например, при сканировании всех чисел до 2000 мы получим следующий результат:

Результат для Max = 10
Нет результата

Результат для Max = 63
1: X = 4, Y = 13

Результат для Max = 867
1: X = 4, Y = 13
2: X = 4, Y = 61

Результат для Max = 1503
1: X = 4, Y = 13
2: X = 4, Y = 61
3: X = 32, Y = 131

Результат для Max = 1967
1: X = 4, Y = 13
2: X = 4, Y = 61
3: X = 16, Y = 73
4: X = 32, Y = 131

Конец вычислений. Время вычислений: 0:00:29

Видно, что решение 4, 13 есть уникальным в диапазоне от 63 до 866.

Кстати, задача, опубликованная в журнале «Наука и Жизнь», не имеет решения вообще, так как там было условие, что числа меньше 60. Представляю, сколько времени угробили читатели пытаясь решить задачу, а она не имеет корректного решения…

Готов выложить листинги программы и саму программу (написана на Delphi), если это возможно.

Комментарии (21)


  1. KvanTTT
    13.05.2015 11:07
    +4

    Хаб: .NET

    Готов выложить листинги программы и саму программу (написана на Delphi), если это возможно.

    Как так то??


    1. vedenin1980
      13.05.2015 11:27
      +2

      Ну, вообще Delphi for .NET существует уже лет 10, видимо на нем и написана программа.


      1. Ununtrium
        13.05.2015 14:16
        +9

        Delphi for .NET

        Месье знает толк.


  1. REZ1DENT3
    13.05.2015 11:28
    +5

    Задача уже была на хабре, habrahabr.ru/post/256023, от 17 апреля 2015
    Неужели задача такая сложная чтобы про неё писать по 2 раза в месяц?


    1. frol
      13.05.2015 13:00
      -2

      Как видно из той статьи — предложенное решение было неверно.


      1. REZ1DENT3
        13.05.2015 13:59
        +2

        Там есть

        UPD2: Для полноты приведу взятый из этой статьи ответ. Исходные числа — 13 и 4. Ответ единственен.
        P.S. Идите в статью, там еще и сорцы есть.


        Те, думаю этого достаточно для одной задачи


        1. frol
          13.05.2015 14:10
          +1

          Я не «за» и не «против». Хотят — пусть пишут. У них же не копипаст, а всё-таки более-менее разные подходы.


  1. GalaxGT Автор
    13.05.2015 17:22
    -2

    Все написанные до сих пор варианты решения задачи были не полные и не до конца корректные. Хотя погрешность такая малая, что она не влияет на окончательный результат для чисел меньше 100. Но она может оказать значительный эффект для больших чисел и в любом случае дает не правильные граничные точки для новых решений. Моя же программа учитывает все нюансы и дает точный результат для любых больших чисел. Мы же хотим узнать точные значения граничных точек, а не приблизительные.
    В англо-язычной литературе я нашел решение этой задачи (правда там в условиях сумма чисел меньше заданного, а не каждое число меньше, как у нас) рассчитанное для больших чисел. Так вот мои решения совпадают полностью (проверял до числа 10000).


    1. REZ1DENT3
      13.05.2015 17:41
      -4

      Моя же программа учитывает все нюансы и дает точный результат для любых больших чисел

      не будьте в себе так уверены! Мантисса у вас бесконечная? Почитайте любую книжку по численным методам(того же Вержбицкого) и вы узнаете, что ваше «точное» решение является не таким уж и точным. То, что программу вы до 10 тыс. проверили это не говорит о том, что она на 40 тыс. не отвалится ;) Ограничение памяти, ограничение языковое…
      ЗЫ Старайтесь уходить от острых моментов)


  1. elepner
    14.05.2015 07:57
    +2

    Решения данной задачи уже были на гиктаймсе, правда в комментариях к статье про др Шерил: моё и товарища omikad.
    Лично я считаю, что задача действительно простовата для отдельной статьи. А мне вот интересен факт, имеется ли максимальное решение у данной задачи? И если есть/нет, то как доказать?
    И еще интересно, как вы умудрились написать код на делфи, который решает задачу для Max=2000 за 29 секунд? Самый тупой брутфорс на C# у меня решил задачу за 30 сек для Max=5000 (на i5 4590). Время для 2000 — 2,5с.


  1. Spiceman
    14.05.2015 14:50
    +3

    Нужно еще больше статей про эту задачу!
    Вот тут решения на разных языках habrahabr.ru/post/256293
    «Готовой компьютерной программы, позволяющей решать такие задачи при любом заданном максимальном числе, обнаружить не удалось.»
    Из статьи не понятно, что за ограничения других программ не позволяют решать эту задачу при любом заданном максимальном числе. Вот возьми, например, любое решение из приведенной мной ссылки и объясни, чем плохо решение.


  1. GalaxGT Автор
    14.05.2015 16:05
    -4

    Решение для Max=5000 делается за 52 сек. Правда у меня компьютер старенький (i3 2.93Ghz). Вот скриншот программы:

    Заголовок спойлера
    image


    1. Spiceman
      15.05.2015 02:16
      +2

      У меня изначально не было цели написать решение для диапазона в 5000 чисел. Программа быстро решала для 100 и этого было достаточно. Чтобы решить для 5000 пришлось запросы немного переделать. Но опять же, цели написать оптимальное решение нет. Цель — написать код в несколько строк :)

      Решение на C#
      using System;
      using System.Collections.Generic; // for HashSet
      using System.Diagnostics;
      using System.Linq;
      class Program
      {
      	static void Main()
      	{
      		var birthDays = Enumerable.Range(2, 4999)
      			.Join(
      				Enumerable.Range(2, 4999), n => 1, n => 1,
      				(n1, n2) => new {D = n1*n2, M = n1 + n2, N1 = n1, N2 = n2})
      			.Where(d => d.N2 >= d.N1).ToList();
      		var x1 = birthDays.GroupBy(d => d.D).Where(g => g.Count() == 1).SelectMany(g => g).ToList();
      		var monthWithUniqueDays = new HashSet<int>(x1.Select(d => d.M));
      		var x2 = birthDays.Where(d => !monthWithUniqueDays.Contains(d.M)).ToList();
      		var x3 = x2.GroupBy(d => d.D).Where(g => g.Count() == 1).SelectMany(g => g).ToList();
      		var solve = x3.GroupBy(d => d.M).Where(g => g.Count() == 1).SelectMany(g => g).ToList();
      		Console.WriteLine(string.Join(", ", solve.Select(d => d.N1 + " " + d.N2)));
      	}
      }
      


      1. GalaxGT Автор
        15.05.2015 03:47
        -2

        Поразительная по краткости программа! На Delphi такое не написать.
        Результаты по парам совпадают. Вот граничное значение не совпало. У меня при Max=866 одна пара, а при Max=867 уже две пары решений. Может быть проверишь еще для 865? Или каким-то способом определи при каком максимальном Max только одно решение имеет задача. Интересно разобраться — может я где-то вместо строго-меньше поставил меньше-равно или еще какая опечатка.
        Для ясности — когда я считаю для Max=N, то имею ввиду, что числа строго-меньше этого N.


        1. Spiceman
          15.05.2015 10:10

          «Для ясности — когда я считаю для Max=N, то имею ввиду, что числа строго-меньше этого N.»
          Тогда все совпадает.


  1. GalaxGT Автор
    14.05.2015 16:16
    -3

    Вот картинка:

    Заголовок спойлера
    image


  1. GalaxGT Автор
    14.05.2015 16:23
    -3

    «Готовой компьютерной программы..." — я имею ввиду готовое приложения, которое может запустить любой пользователь (не владеющий навыками программирования и не имеющий никаких компиляторов). При этом можно вводить интересующее максимальное число и получать наглядный результат с комментариями.


    1. vedenin1980
      14.05.2015 16:33

      Простите, а зачем это любому пользователю? Собственно, обычному пользователю хватит ответа на оригинальную загадку, а знать магическое число при ограничении в 1 млн. это тоже самое что интересоваться точным значением числа пи с 1 млн. до 1 миллиардного знака.


  1. GalaxGT Автор
    15.05.2015 01:01
    -2

    Немного оптимизировал программу и теперь расчет для Max=5000 длится 8 сек.
    У меня просьба к тем у кого есть подобные программы. Могли бы вы отписаться о результатах теста? Это чтобы удостовериться, что и у вас и у меня программы оттестированные и дают одинаковый результат. В качестве теста предлагаю посчитать для следующих условий:
    — Числа меньше заданного Max;
    — Числа могут быть одинаковые;
    Сколько решений для:
    1. Для Max = 5000 (и желательно какое время вычислений);
    2. Для Max= 866 и для Max=867.


    1. mlg
      17.05.2015 03:00
      +2

      Вот вам покоя не было от этой задачи.
      У меня с предыдущего подхода код на JavaScript оставался. Так и пришлось тоже доделать.

      Код
      <document html><head>
      
      <script>
      
      var MAX=5000;
      
      var P, W, i, j, k, A, B;
      var M=[], Z=[];
      
      for (i=0; i<(MAX*MAX); i++) M[i]=0;
      for (i=2; i<MAX; i++) for (j=i; j<MAX; j++) { P=i*j; M[P]++; }
      
      for (W=4,k=0; W<(2*MAX-2); W++) {
        P=0;
        for (j=2; j<=W/2; j++) if (M[j*(W-j)] === 1) { P=1; break; }
        if (P===0) { Z[k]=W; k++; }
      }
      
      /*Здесь k - количество уникальных сумм*/
      
      for(i=0; i<(MAX*MAX); i++) M[i]=0;
      
      /*Массив количеств возможных произведений*/
      for(i=0; i<k; i++)  for(j=2; j<=Z[i]/2; j++)  M[(Z[i]-j)*j]++;	
      
      /*Вывод списка*/
      for(i=0; i<k; i++) { 
      	W=0;
      	for(j=2; j <= Z[i]/2; j++) if (M[(Z[i]-j)*j] === 1) {
      		W++; P=(Z[i]-j)*j; A=j; B=Z[i]-j;
      	}
      	if (W===1) {
      document.write("A=",A,",  B=",B,",  Sum=",Z[i],",  Mul=",P);
      document.write("<br>");
      
      //console.log("A="+A+",  B="+B+",  Sum="+Z[i]+",  Mul="+P);
      	}
      }
      
      </script>
      
      </head><body></body></html>
      


  1. GalaxGT Автор
    17.05.2015 23:46
    -2

    Я эту задачу впервые увидел не на этом ресурсе и намного позже, чем она тут обсуждалась. Поэтому пропустил все ваши обсуждения. Начал гуглить решения и наткнулся на англо-язычную статью, где солидные профессора обсуждают на 30-ти стр. эту проблему, выдвигают какие-то гипотезы и строят теоремы, выводят таблицу граничных точек для Max=50000 и т.д. Все это для условия задачи, где сумма чисел не больше заданного Max. Мне стало интересно будет ли кардинально задача отличаться, если в условиях будет каждое число меньше Max и заодно узнать граничные точки для этих условий. Также мне было интересно поупражняться в алгоритмах и проверить себя. Результатом стала эта программа и на многие вопросы я получил ответы.
    Когда я захотел поделиться результатами с сообществом, то решил сделать это на этом ресурсе, так как считаю его наиболее подходящим местом. Я прошу прощения, но это был мой первый пост здесь и я не очень знаком с здешними правилами. Неожиданно для себя я нахватал кучу негатива в виде " А кому это надо?". Действительно, никому оно не надо, пускай и дальше этим занимаются только иностранные ученые, им видимо заняться больше нечем.
    И наконец вопрос программистам. Если бы, ученые поставили вам задачу, решить эту проблему для наиболее большого числа Max и за наиболее короткое время? Понятно, что эта задача не имеет практического смысла. Но в качестве упражнения по оптимизации алгоритмов?
    Ведь в какой-то момент массив чисел размерностью Max*Max не поместится в память. Как можно поднять планку для Max для обычного персонального компьютера?
    Запустил я на ночь прогу с Max=60000 — результат 27 пар чисел, время 2часа 28 мин. Интересно какой предел компьютера?