关于取模与Mint模板

一、关于取模运算

1.1 关于本篇内容

在做题的时候总会遇到很多需要取模的结果,让答案对取 \(1e9 + 7\) 或者是 \(998244353\) 这样的数字取模。这两个数都是质数!我们这篇主要是要说明为什么取模的时候对于除法、减法需要考虑逆元。以及对于逆元应该如何实现。

1.2 关于常见的取模等价形式

  • $(a * b) % p = (a % p * b) % p $

  • \((a + b)\% = (a \%p + b)\%p\)

  • \((a + b) \% p = (a \% p + b \% p) \% p\)

  • $(a * b) % p = (a % p * b% p) % p $

  • \((a - b)\% p = (a \% p - b \% p) \% p\)

1.3 为什么在减法运算和除法运算中要考虑加法逆元于乘法逆元

在我们做运算的是免不了用除法、减法来进行公式的运算。比如说以下情况

\[21/7\%5=3 \]

对于上面这个式子来说,这么运算是完全没有任何问题的。但是当我们数大了以后就会根据上文所述的等价形式进行类比推理取模,比如

\[21\%5/7\%5=0 \]

但是我们会发现,在进行取模后,由于\(21\%5=1\)会导致计算出的结果出问题了。

同理在我们,减法中,也常会用加法逆元来实现。避免负数的出现,比如下面的例子

\[(37 - 5)\%7=4\\ \]

\[(37 \% 7 - 5\%7)= (2-5)=-3 \]

我们会发现明显出现了负数的情况,所以我们为了避免溢出、出负数、除 \(0\) 的情况我们将采用加法逆元、乘法逆元的方式来进行计算。

1.4 加法逆元

我们加法逆元可以直接通过以下公式来转换,因为对于一个整数的逆元可以通过以下公式实现

\[(a - b)\%p=(a\%p + (p - b\%p))\%p \]

这样不仅可以避免溢出,也可以避免减法产生的负数存在。

\[a\% p\geq0\\ \]

\[b\%p\lt p\\ \]

\[综上可得:a\%p + (p - b\%p) \ge0\\ \]

\[(37\%7) + (7 - 5 \%7) = (2 + 2) = 4 \]

1.5 乘法逆元

若存在整数 \(b,m\),且两数互质,并且对于任意的整数 \(a\) ,若满足 \(b|a\),则存在一个整数 \(x\),使得 \(\frac{a}{b} \equiv a \times x(modx)\) ,则成 \(x\)\(b\)的模 \(m\)的乘法逆元。

\[(6 / 2)\%5\equiv(6*3)\%5 \\ \]

这里等于乘3,就是因为3是2的模5乘法逆元。

\[2 \times3\equiv1(mod5) \]

所以对于乘法而言,我们就会将触发转发为乘法逆元来避免除0。

二、关于乘法逆元求法

大家根据1.4很容易就可以得出加法逆元的修改方式,就是把减法改为上述的式子。那么对于乘法逆元来说,我们应该如何求呢?

这里我们会用到一个定理,即费马小定理:如果p是一个质数,而整数 \(a\) 不是 \(p\) 的倍数,则有 \(a^{(p-1)}≡1(mod p)\)

那么我们先把定理放这里,我们先从乘法逆元的概念上来想下,要如何求乘法逆元。

对于求某个数的乘法逆元 \(\frac{a}{b} \equiv a \times x(modx)\) 那么在这个式子里面,\(x\) 就是 \(b\) 的乘法逆元,我们要求的就是 \(x\),那么式子其实可以通过约分化简得到,最后目标转化成了 \(b\times x\equiv1(mod m)\) 找这个 \(x\) 是多少,那么我们由于一般题面里面取模的 \(m\) 都是质数,所以这个条件会满足,我们就可以看到上文的费马小定理

\[a^{(p-1)}≡1(mod p)\\ b\times b^{p-2}\equiv1(modp) \]

所以我们发现只要求出来\(b^{p-2}\%p\)即可,那么这个问题就直接转换成了一个快速幂了,所以我们直接用快速幂的板子来求逆元即可。

ll qmi(ll a, ll b, ll p)
{
	ll res = 1;
	while(b)
	{
		if(b & 1) res = res * a % p;
		b >>= 1;
		a = a * a % p;
	}
	return res;
}

void solve()
{
	ll a, b, p;
	std::cin >> a  >> p;
	std::cout << qmi(a, p - 2, p) << endl;	// 这个就是求出来a模p的逆元
	return;
}

三、Mint自动取模

上述方法可以通过逆元的方式来解决掉我们大数减法、除法的负数、除 \(0\)问题。但是在写代码的时候会发现产生大量的 \(mod\)运算,括号带了一个又一个。很不美丽,我们就可以用Mint自动取模,重载一下就可以自动模\(P\)相当优美

template<class T>
constexpr T power(T a, LL b) {
    T res = 1;
    for (; b; b /= 2, a *= a) {
        if (b % 2) {
            res *= a;
        }
    }
    return res;
}

template<int P>
struct MInt {
    int x;
    constexpr MInt() : x{} {}
    constexpr MInt(LL x) : x{norm(x % getMod())} {}

    static int Mod;
    constexpr static int getMod() {
        if (P > 0) {
            return P;
        } else {
            return Mod;
        }
    }
    constexpr static void setMod(int Mod_) {
        Mod = Mod_;
    }
    constexpr int norm(int x) const {
        if (x < 0) {
            x += getMod();
        }
        if (x >= getMod()) {
            x -= getMod();
        }
        return x;
    }
    constexpr int val() const {
        return x;
    }
    explicit constexpr operator int() const {
        return x;
    }
    constexpr MInt operator-() const {
        MInt res;
        res.x = norm(getMod() - x);
        return res;
    }
    constexpr MInt inv() const {
        assert(x != 0);
        return power(*this, getMod() - 2);
    }
    constexpr MInt &operator*=(MInt rhs) & {
        x = 1LL * x * rhs.x % getMod();
        return *this;
    }
    constexpr MInt &operator+=(MInt rhs) & {
        x = norm(x + rhs.x);
        return *this;
    }
    constexpr MInt &operator-=(MInt rhs) & {
        x = norm(x - rhs.x);
        return *this;
    }
    constexpr MInt &operator/=(MInt rhs) & {
        return *this *= rhs.inv();
    }
    friend constexpr MInt operator*(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res *= rhs;
        return res;
    }
    friend constexpr MInt operator+(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res += rhs;
        return res;
    }
    friend constexpr MInt operator-(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res -= rhs;
        return res;
    }
    friend constexpr MInt operator/(MInt lhs, MInt rhs) {
        MInt res = lhs;
        res /= rhs;
        return res;
    }
    friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
        LL v;
        is >> v;
        a = MInt(v);
        return is;
    }
    friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
        return os << a.val();
    }
    friend constexpr bool operator==(MInt lhs, MInt rhs) {
        return lhs.val() == rhs.val();
    }
    friend constexpr bool operator!=(MInt lhs, MInt rhs) {
        return lhs.val() != rhs.val();
    }
};

template<>
int MInt<0>::Mod = 998244353;

template<int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();

constexpr int P = 1000000007;
using Z = MInt<P>;
posted @ 2024-11-07 21:39  wxzcch  阅读(72)  评论(0编辑  收藏  举报