Блог пользователя vml

Автор vml, история, 8 лет назад, По-русски

Здравствуйте, сегодня я хотел бы рассказать об одном способе как можно упростить вычисления с длинной арифметикой. Допустим вам в задаче нужно посчитать некоторую величину, длину которой вы можете оценить сверху величиной len. Но возможно, чтобы посчитать эту величину требуется длинное умножение, сложение, вычитание, или еще какие-нибудь другие не простые операции. Предположим что ваша программа работает за T * len, тогда можно заменить это решение на другое, почти не использующее длинной арифметики и работающее за T * len / 9 + (len / 9) ^ 2. Чтобы сделать это воспользуемся Китайской теоремой об остатках. Выберем len / 9 некоторых различных простых чисел порядка 1000000000, и посчитаем ответ на нашу задачу по модулю каждого из данных простых за T * len / 9. Китайская Теорема об остатках утверждает, что существует единственное число меньшее чем произведение этих простых, у которого ровно такие остатки. Восстановить такое число не трудно, будем находить такое число по очереди для каждого префикса простых. На первом шаге наш ответ просто равен остатку. Далее на очередном шаге у нас есть некоторое число answer для предыдущего префикса и есть очередной остаток a и очередное простое число p[i]. Тогда достаточно решить уравнение: answer + x * p[0] * p[1] * ... * p[i — 1] = a(mod p[i]), такое x нетрудно найти, например, обратив p[0] * p[1] ... * p[i — 1] по модулю p[i]. Тогда новый answer будет равен answer + x * p[0] * p[1] * ... * p[i — 1]. Таким образом вместо всей длинной арифметики у нас осталось лишь умножение длинного числа на короткое. Более того этот способ может помочь если у вас были проблемы с памятью, так как если бы у вас раньше было памяти M * len, то сейчас можно обойтись M + len / 9. Сам я использовал этот способ в некоторых задачах и он действительно помогал мне избежать некоторых проблем. Ссылки на задачи: http://acm.timus.ru/problem.aspx?space=1&num=1467 http://acm.timus.ru/problem.aspx?space=1&num=1677

  • Проголосовать: нравится
  • +80
  • Проголосовать: не нравится

»
8 лет назад, # |
  Проголосовать: нравится +28 Проголосовать: не нравится

Звучит неплохо. Особенно часть про память.

Про время работы — в формальной оценке непонятно, откуда взялась константа 9. Если имеется в виду, что стандартная реализация длинной арифметики на соревновании держит по одной десятичной цифре в int32, то обычно это не так: можно хранить целых 9 цифр, код усложняется не сильно.

С другой стороны, код с умножением работал за T·len2, или (с Фурье) за , а станет за T·len без логарифма, так что ускорение действительно получится.

»
8 лет назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

А есть какие-нибудь способы восстановить число по КТО за ? Или предпосылки к тому, что такого нет и быть не может?

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +11 Проголосовать: не нравится

    Не понял вопроса, разве мое восстановление работает не за o(len^2)?

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +8 Проголосовать: не нравится

    «Разделяй и властвуй» плюс Фурье дает , если игнорировать необходимость вычислять обратные по модулю, для которых субквадратичные алгоритмы тоже вроде как существуют: Binary Recursive GCD.

»
8 лет назад, # |
  Проголосовать: нравится -12 Проголосовать: не нравится

Какие-то странные примеры. Я посмотрел свои решения, так вот в 1467 вообще нет длинной арифметики, а в 1677 и без КТО хватает умножения на короткое.

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +10 Проголосовать: не нравится

    Я привел примеры где я лично использовал эту идею, очевидно можно обойтись и без неё, в 1677 у меня были проблемы как раз с памятью, а в 1467 я писал решение которое использовало длинную арифметику.

»
8 лет назад, # |
  Проголосовать: нравится +38 Проголосовать: не нравится

Только делить надо аккуратно. Например, если вам случайно захотелось поделить на ноль по модулю, то будут серьёзные проблемы (скажем, если знаменатель получился как сумма двух промежуточных результатов и оказался равен простому числу). Если есть только умножения и деления, то это лечится хранением степени модуля отдельно (т.е. x = mk·y хранится как — такие пары всегда можно умножать и делить, если делится).

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    У меня вроде происходит деление какого-то числа(возможно нулевого) на (p[0] * p[1] * ... * p[i — 1]) по модулю p[i], но произведение простых не может делиться на другое простое, или я что-то понял не так?

    • »
      »
      »
      8 лет назад, # ^ |
      Rev. 4   Проголосовать: нравится +10 Проголосовать: не нравится

      Вот было у вас число 3 хранилось как (3,3,3,3,3,...)

      Теперь умножьте на p[0], будет (0, 3p[0]%p[1], 3p[0]%p[2], ...)

      Теперь поделите на p[0]

      Если бы вы это делали в длинке, получили бы 3

      • »
        »
        »
        »
        8 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        А можно конкретный пример, если не трудно, на котором мой алгоритм не работает? Если answer = 3, мой алгоритм это находит, на каждом шаге он решает уравнение 3 + x * p[0] * ... * p[i — 1] = 3(mod p[i]) <=> x * p[0] * p[1] * ... * p[i — 1] = 0(mod p[i]) <=> x = 0 <=> new_answer = answer + 0 * p[0] * ... * p[i — 1] = answer.

        • »
          »
          »
          »
          »
          8 лет назад, # ^ |
          Rev. 2   Проголосовать: нравится +20 Проголосовать: не нравится

          Еще раз, я не про вычисление по КТО в конце, я про то, что происходит при вычислениях по модулю.

          В моем примере (я не понимаю куда конкретнее) (Берем 3, умножаем на p0, делим на p0) вы будете делить 0 на 0 по модулю p0

          • »
            »
            »
            »
            »
            »
            8 лет назад, # ^ |
              Проголосовать: нравится +25 Проголосовать: не нравится

            А, все понял, я просто думал, что проблема в последней части, да с этим действительно надо быть аккуратным.

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится +50 Проголосовать: не нравится

    Может быть можно набрать модулей чуть с запасом, и потом восстанавливать ответ по тем, по которым не было деления на 0?

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится +23 Проголосовать: не нравится

      При этом, видимо, стоит модули случайными брать, чтобы тест явным образом не крафтился

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Не совсем понятно что вы и AlexDmitriev имеете в виду. Если есть в вычислениях деление, то в общем случае нельзя производить вычисления только с остатками, и не только если деление на сам модуль.

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится +3 Проголосовать: не нравится

      Можно, если делим на что-то, взаимно простое с модулем. Просто это надо воспринимать не как "деление", а как "домножение на обратный элемент", который много когда существует и единственен. Статья на e-maxx.

»
8 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

Можно поподробнее про то, как осуществляется деление в этом представлении?

  • »
    »
    8 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    умножение на обратное

    • »
      »
      »
      8 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Это целочисленное деление по модулю, а как делать деление с остатком: нахождение модуля частного и остатка от деления?

      • »
        »
        »
        »
        8 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

        Никак, остатка от деления на одно число не достаточно для того, чтобы что-то сказать об остатке/неполном частном при делении на другое

»
8 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

Это работает и КТО эффективнее обычной реализации? Совершенно не понял про выигрыш по памяти относительно длинной арифметики. Вы сравниваете с арифметикой, в которой десятичный разряд хранится в 4 байтах? Почему бы просто 9 десятичных разрядов не хранить в 4 байтах?

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