Часть I. Конечные автоматы


Речь пойдет об универсальном способе построения конечных автоматов делимости. Он основывается на двух простых мыслях: число m делится на число n без остатка, тогда и только тогда, когда запись числа m в системе счисления по основанию n оканчивается нулем; когда мы дописываем к записи числа цифру, это равносильно умножению этого числа на основание системы счисления и последующему прибавлению числа, выраженного дописанной цифрой (например, к числу 1001 в двоичной системе счисления [10012=910] мы дописываем цифру 1, это значит что данное число мы умножаем на два и прибавляем 1 [100112=910*210+1]).

Другими словами, запись числа m в системе счисления по основанию n есть запись в обратном порядке остатков при последовательном делении на n (см. ниже пример перевода числа 384 в систему счисления по основанию 7 и код на python перевода числа m в систему счисления по основанию n):

(384-6)/7=54
(54-5)/7=7
(7-0)/7=1
(1-1)/7=0

Теперь запишем остатки в обратном порядке: 10567=38410

Код на python

m=int(input('A number: '))
n=int(input('A numeral system: '))
l=[]
while(m>0):
  l.append(int(m%n))
  m=m-(m%n)
  m=m/n
print(l[::-1])

Но вернемся к конечным автоматам. Для удобства я возьму в качестве примера построение конечного автомата делимости на 13, но алгоритм построения будет аналогичен для любого другого числа. Систему счисления возьмем троичную, но — аналогично — для любой другой системы счисления алгоритм будет тот же.

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

  1. выделить n состояний автомата (в данном случае 13), каждое из которых соответствует остатку от деления на n (от 0 до n-1);
  2. для каждого состояния прописать переходы по следующему принципу (допустим, мы прописываем переходы для состояния 5):

   если цифра 0, то (5*основание системы счисления)%n, где в нашем случае 
основание системы счисления равно 3, а n=13. Получается: (5*3)%13=2, то есть 
из состояния 5 по цифре 0 автомат переходит в состояние 2
   если цифра 1, то (5*основание системы счисления+1)%n. Получается: (5*3+1)%13=3
   если цифра 2, то (5*основание системы счисления+2)%n. Получается: (5*3+2)%13=4

Таким образом, будет получен автомат делимости на число n.

Как это работает? Возьмем наугад вот такое число: 201012213. Теперь начинаем движение по конечному автомату: состояние 2 -> состояние 6 [2*3+0] -> состояние 6 [(6*3+1)%13] -> состояние 5 [(6*3+0)%13] -> состояние 3 [(5*3+1)%13] -> состояние 11 [3*3+2] -> состояние 9 [(11*3+2)%13] -> состояние 2 [(9*3+1)%13]. Следовательно, остаток числа 201012213 от деления на 1310 равен 2.

Часть II. Сразу две делимости


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

Для простоты рассмотрим конечный автомат делимости на числа 2 и 3. Он имеет следующие состояния: 00 (число делится и на 2, и на 3), 01 (делится на 2, остаток деления на 3 равен 1), 02 (делится на 2, остаток деления на 3 равен 2), 10 (нечетное и делится на 3), 11 (остатки от деления на 2 и 3 равны по 1), 12 (нечетное, остаток от деления на 3 равен 2). Систему счисления возьмем двоичную.



Строя автомат делимости для двух чисел 2 и 3, мы также неизбежно построили автомат делимости на число 6 (их произведение). Действительно, состояние 00 это состояние 0 автомата делимости на 6; 01 — состояние 4; 02 — 2; 10 — 3; 11 — 1; 12 — 5.

Иными словами, за каждым автоматом делимости полупростого числа стоит автомат делимости его простых множителей. Этот факт я хотел использовать при факторизации чисел.

Часть III. Отважная попытка


Итак, у нас есть число s, которое является произведением двух простых чисел p и q. Так как число s является большим, автомат делимости с s состояниями практически построить невозможно из-за физических ограничений, но мы можем рассуждать, как будто он у нас есть и перемещаться по нему как нам вздумается. Сразу скажу, что решить проблему факторизации мне не удалось. Было несколько разных подходов к «вытаскиванию» из автомата делимости на число s автомат делимости на числа p и q (из части II мы знаем что это «две стороны одной и той же медали»), но я опишу лишь тот, который, похоже, ближе всех подошел к истине.

Для примера возьмем s=15707. Действовать будем в двоичной системе.

Шаг 1. (s-1)/2=7853. Рассуждаем, какое состояние скрыто в состояние 7853 с точки зрения автомата делимости на числа p и q. Нетрудно догадаться, что s-1 соответствует состояние p-1|q-1, а (s-1)/2 — соответствует состояние (p-1)/2|(q-1)/2. Говоря другим языком, 7853%p=(p-1)/2

Шаг 2. (7853-1)/2=3926. Какое же состояние стоит за числом 3926? Первое, что напрашивается на мысль это то, что нужно из предыдущего остатка также вычесть 1 и поделить все на два. ((p-1)/2-1)/2=(p-3)/4. Но не все так просто, выражение (p-3)/4 имеет право на существование только в том случае, если p%4=3. Обратимся к числу s: s%4=3, следовательно у одного из его делителей остаток от деления на 4 равен 3, а у другого — равен 1. Поэтому выражение (p-3)/4 можно считать справедливым, но что тогда произошло с q в состоянии 3926?

Благодаря тому, что мы выбрали двоичную систему счисления, мы можем записать следующее равенство: (q-1)/2=2*x-q+1, где x=3926%q. Решив равенство, получим x=(3q-3)/4. Таким образом, за числом 3926 скрыто состояние (p-3)/4|(3q-3)/4.

Как видите, если бы мы могли двигаться дальше, рано или поздно мы бы пришли к тому состоянию, когда число будет меньше p, и тогда наш остаток на тот момент можно будет приравнять к числу, благодаря чему можно будет легко найти p. Но двигаться дальше (во всяком случае, двигаться детерминировано) мы можем только до тех пор, пока цифра перемещения по автомату не равна 0 (см. шаг 3).

Шаг 3. s/2=1963. Чтобы сказать какое состояние скрыто за числом 1963 нужно знать p%8 и q%8.
s%8=3, а это значит что p%8=3 и q%8=1 или же p%8=7 и q%8=5.

Если мы примем, что p%8=7, то получим остаток (5p-3)/8 [(p-3)/4=2*x-p, где x=1963%p]. 5*7-3 делится на 8, следовательно предположение может быть верным. Тогда как если p%8=3, то 1963%p=(p-3)/8, что тоже может быть верным. Таким образом, каждый встречающийся ноль [шаг (s-0)/2] раздваивает наши предположения.

Но если бы всегда только ноль, данный способ годился бы для чисел Мерсенна. Однако это не так, так как единицы порой тоже рождают неопределенность.

Итог


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

Автор: Руслан Фаткуллин

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


  1. EvilGenius18
    21.08.2018 17:03
    +1

    Непонятно в чем преимущество этого метода факторизации. Быстрее ли он устоявшихся методов?


    1. ftk518 Автор
      21.08.2018 17:33

      Если говорить про практическое применение, то я вижу только одну полезность здесь изложенного: в ситуации когда само число огромно (например, несколько миллионов знаков), а мы проверяем его делимость только на относительно небольшие делители.
      Статья — просто скромная попытка взглянуть на проблему факторизации по-новому.


  1. Sirion
    21.08.2018 19:01
    +1

    1. ftk518 Автор
      22.08.2018 09:09

      Здесь гораздо круче. Вы, видимо, не вникали в суть.
      Попробуйте взять число с миллионом знаков и, например, найти остаток на 137 по признаку Паскаля.


      1. Sirion
        22.08.2018 17:00

        Давайте число) не вижу никаких проблем.


        1. ftk518 Автор
          22.08.2018 19:33

          23 021 377


          1. Sirion
            22.08.2018 21:31

            Батенька, это я в уме могу найти) Остаток 2. Малая теорема Ферма.


          1. Sirion
            23.08.2018 00:07

            Почему вы вообще полагаете, что у меня должны возникнуть проблемы? Вычисление по признаку Паскаля займёт линейное время по количеству цифр делимого и линейную память по величине делителя. Собственно, я скажу даже больше: признак Паскаля, если выполнять по нему reduce — это по сути и есть ваш автомат.


          1. bopoh13
            23.08.2018 00:31

            Я понял, о чём вы. Можно было предложить и поменьше 21237-1.

            Ссылка выше описывает универсальный признак делимости
            Чтобы узнать остаток от деления числа в миллион знаков на 137, нужно вычислить ряд остатков (до их цикличного повторения) и найти сумму чисел каждого разряда (начиная с единиц) перемноженного на полученный ряд остатков.
            m = 137
            r = 1
            
            for i in range(1, m):
              # python: без проверки повторения остатков
              if r == 1 and i > 1:
                break
              print("r%i" %(i-1), "=", r)
              r = 10*r %m

            m = 137
            r = 1
            
            for i in 1..m
              # ruby: без проверки повторения остатков
              break if r == 1 and i > 1
              puts "r#{i-1} = #{r}"
              r = 10*r %m
            end
            Если полученное число делится на 137 без остатка, то и исходное число делится без остатка.


            1. ftk518 Автор
              23.08.2018 05:35

              На мой взгляд, так гораздо проще

              n=137
              a=1
              i=0
              while i<3021377:
                a=(a*2)%137
                i=i+1
              print(a)
              


  1. saipr
    22.08.2018 09:40
    -1

    Я помню тоже доказывал теорему Ферма.


  1. vdvvdv
    22.08.2018 15:42

    На шаге 3 комбинации могут быть не только p%8=3 и q%8=1 или же p%8=7 и q%8=5, но и наоборот p%8=1 и q%8=3 или же p%8=5 и q%8=7. Т.е. один из множителей имеет остаток 1,3,5 или 7 по модулю 8, то есть нечетное. Это ничего не даст, так как умножение коммутативно.
    И будет не раздваиваться, а множествено ветвиться при больших модулях (примерно m/2 ветвей, но не в случае произведения).
    Лучше использовать метод разложение Ферма n=x^2-y^2 = (x-y)*(x+y). Использовать свойства квадратичных вычетов через символы Якоби и Лежандра. Найти такие остатки по модулю m, что x^2-n по этому модулю является квадратичным вычетом. Тогда число x будет ограничиваться количеством вариантов остатков примерно равным m/2.


    1. vdvvdv
      22.08.2018 16:14

      2) будут холостые ветви, где множители будут равны 1 и n.
      3) при анализе умножения по кратным множителям будут тривиальные остатки. При модуле 4 остаток 1 равен остатку 1 и 5 по модулю 8.


    1. ftk518 Автор
      22.08.2018 19:55

      На шаге 3 комбинации могут быть не только p%8=3 и q%8=1 или же p%8=7 и q%8=5, но и наоборот p%8=1 и q%8=3 или же p%8=5 и q%8=7

      На шаге 2 мы условились, что p%4=3, а q%4=1. Если число дает остаток n при делении на x, то при делении на 2x оно даст либо тот же остаток, либо n+x. Другими словами, если p%4=3, то p%8 равно либо также 3, либо 7 [3+4].
      Лучше использовать метод разложение Ферма...

      Насколько я понимаю, сейчас самыми эффективным методами факторизации являются общий метод решета числового поля и метод квадратичного решета [дериватив метода Ферма], и я не оспариваю это. Вопрос был в том, можно ли взглянуть на проблему иначе, что я и попытался сделать.


  1. vdvvdv
    22.08.2018 21:49

    Да, при модуле m и 2m остатки те же самые. Исключения, уменьшения выбора не получилось.
    Иного взгляда врядли получилось. Есть библиотека GMP для работы с длинными числами (на Python — gmpy2), там в исходниках можно посмотреть эффективные алгоритмы взятия остатков.
    Если проверять составное число на маленькие множители, то легче перемножить эти малые множители и найти GCD(НОД) через алгоритм Евклида. Или использовать ECM(Ленстра), который эффективно ищет малые множители.


    1. vdvvdv
      22.08.2018 22:09

      Увеличив модуль в два раза, увеличится количество остатков в два раза. Т.е. нет уменьшения выбора. Поэтому и используют квадратичные вычеты. Там, да, увеличив модуль, скажем с 3 до 15(т.е. в 5 раз), количество остатков увеличивается всего лишь примерно в два раза. Там даже исследуя модули степеней двоек(или других простых модулей) можно сократить количество остатков.


    1. vdvvdv
      22.08.2018 22:26

      Например, используя пакет GAP:
      gap> n:=19*97;
      1843
      gap> m:=4;List([0..m-1],e -> Legendre((e^2-n) mod m,m));
      [ 1, -1, 1, -1 ]
      gap> m:=8;List([0..m-1],e -> Legendre((e^2-n) mod m,m));
      [ -1, -1, 1, -1, -1, -1, 1, -1 ]
      gap> m:=16;List([0..m-1],e -> Legendre((e^2-n) mod m,m));
      [ -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1, 1, -1 ]

      При модуле 4 остатков 2. При модуле 8 остатков тоже 2. При модуле 16 — 4. Т.е. при переходе с 4 до 8 сократили количество остатков. При переходе с 8 до 16 не сократили.