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

Автор madhur4127, история, 6 лет назад, По-английски

TL;DR This article features Modular class which can be conviniently used for modular arithmetic more "naturally".

Motivation

Recent round featured an interesting problem 1081C - Colorful Bricks (if you plan to solve it, read this later). Combinatoric solution requires you to calculate:

Many contestants failed pretest because of missing modulo operation somewhere in code.

Solution

Many contestants use functions like add(ll a, ll b) or mul(ll a , ll b) which makes implementation somewhat clumsy.

I present you an alternate approach using Modular class.


template <int MOD=998'244'353>
struct Modular {
  int value;
  static const int MOD_value = MOD;

  Modular(long long v = 0) { value = v % MOD; if (value < 0) value += MOD;}
  Modular(long long a, long long b) : value(0){ *this += a; *this /= b;}

  Modular& operator+=(Modular const& b) {value += b.value; if (value >= MOD) value -= MOD; return *this;}
  Modular& operator-=(Modular const& b) {value -= b.value; if (value < 0) value += MOD;return *this;}
  Modular& operator*=(Modular const& b) {value = (long long)value * b.value % MOD;return *this;}

  friend Modular mexp(Modular a, long long e) {
    Modular res = 1; while (e) { if (e&1) res *= a; a *= a; e >>= 1; }
    return res;
  }
  friend Modular inverse(Modular a) { return mexp(a, MOD - 2); }

  Modular& operator/=(Modular const& b) { return *this *= inverse(b); }
  friend Modular operator+(Modular a, Modular const b) { return a += b; }
  friend Modular operator-(Modular a, Modular const b) { return a -= b; }
  friend Modular operator-(Modular const a) { return 0 - a; }
  friend Modular operator*(Modular a, Modular const b) { return a *= b; }
  friend Modular operator/(Modular a, Modular const b) { return a /= b; }
  friend std::ostream& operator<<(std::ostream& os, Modular const& a) {return os << a.value;}
  friend bool operator==(Modular const& a, Modular const& b) {return a.value == b.value;}
  friend bool operator!=(Modular const& a, Modular const& b) {return a.value != b.value;}
};

Using this code can be written more naturally in modular field just like integers. This implementation simplifies usage, for example:

// Chained Multiplication or Successive Simple Multiplication
Modular<998244353> a=1, m=123456789;
a *= m * m * m; // a = 519994069
// Inverse
a=inverse(m) // a=25170271
// fractions
Modular<> frac=(1,2); // frac=1*2^(-1) % 998244353 = 499122177
// Modular exponentiation
Modular<> power(2);
power=mexp(power,500); // power = 616118644

Credits to Jakube and here's the link to original source. Link: Modular.h

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

»
6 лет назад, # |
Rev. 3   Проголосовать: нравится +11 Проголосовать: не нравится

I have seen similar implementations before, but I'm not sure if performance is not an issue. Do you have benchmarks comparing this to the non-wrapped implementation, in terms of both time and memory usage?

I think having this in a critical loop (which runs, say 10^8 times) might be problematic, but I'm not sure; C++ compilers can do a lot of magic.

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

    that's c++. such abstractions do not cost anything here.

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

      Isn't there overhead for the function call? Or can we be certain it is inlined

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

        you are right, overhead for function calls is possible but for such trivial function I just assume they are inlined (I use such class for several years, didn't have any problem).

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

    I use a similar class. In my experience the abstraction doesn't noticeably reduce efficiency compared to manually applying the same operations, but it can cause you to use more operations that you actually need. In particular, when taking the sum of N numbers, it is more efficient to first sum them in long long, then reduce that sum, than to reduce after every addition.

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

Exactly what Wave said.

Perfomance in many cases of modular tasks is an issue and you should avoid copying by const Modular& rather using plain const Modular (since it doesn't really matter whether you put const here or not, you can keep it if you want compliler to help you a little) in most of your operators.

See this: this. Although being too unrealistic, you should generally avoid passing references to classes where sizeof(class) is around a single pointer due to a perfomance overhead over plain copy of that class.

P.S. I don't see this struct Modular <int N> as a good solution because such class makes it significantly harder to pass ints to functions

P.P.S. and yeah, specialization for long long modulo would've been really cool, since this is something really lame to implement over and over

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

    Since all the methods and friend functions are implemented in the class, they will be inlined by default, and there is no difference in passing argument by reference or by value.

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

integer do not form field btw

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

Your implementation is very limiting. First, only int modulo is supported, no long long or any custom big integer class. Second, it won't work if the modulo isn't fixed, i.e. given in the input file.

I have similar class that can be found for example in these submissions: 26577783 24552744. Indeed, it's very convenient and I never faced any performance issues.

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

    Another issue is that it relies on the modulo being prime (the inverse is found using fermat's).

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

      yes, this works for prime modulo which is very common otherwise you can replace inverse with extended euclid.

      Because it is not very common where you need to find inverse in non-prime modulo (not for problems focused on extended euclid), so focusing on speed fermat is used.

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

    btw you may use std::integral_constant when modulo is constant instead of creating new class manually

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

    1 . Considering general modulo (1e9+7, 9998244353), long long is not supported as multiplication function will change to somewhat like code below, which will not be efficient in case of common integer mod.

    Code

    2 . Submission (26577783) is not visible to me. Can you share code via other methods?

    3 . BigIntegers classes typically contain modulo operation.

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

Here's the result of running the code below in Custom test on Codeforces:

Code

The results (2e8 iterations) are:

Execution time (w/ class): 2338.19 ms.
Execution time (w/o class): 2797.18 ms.
=====
Used: 5116 ms, 0 KB