1. 程式人生 > 其它 >【數學】逆元

【數學】逆元

一、逆元的意義

在一道題中,如果既有除法又有取餘,那麼是不能直接除的:

\[\dfrac{a}{b}\bmod p\ne \dfrac{a\bmod p}{b\bmod p} \]

這時候逆元就登場了.

眾所周知,\(a\cdot a^{-1}=1\),類似地定義 \(a\) 在模 \(p\) 意義下的逆元為使得

\[a\cdot x\equiv 1\pmod p \]

成立的 \(x\).

注意到當 \(\gcd(a,p)>1\) 時上式不可能成立,所以只有 \(a\)\(p\) 互質時才有逆元.

那麼在模 \(p\) 意義下,\(x=a^{-1}\)\(x\) 記作 \(inv(a)\)

.

考慮回剛剛的問題:

\[\dfrac{a}{b}\bmod p=(a\cdot b^{-1})\bmod p \]

所以求出 \(inv(b)\),答案就是 \((a\cdot inv(b))\bmod p\).

二、求單個數的逆元

P2613 【模板】有理數取餘

臭模數

用快讀 \(+\) 取餘讀入.

首先 \(19260817\) 是個質數,所以當且僅當 \(19260817\mid b\)\(b\) 沒有逆元,即無解.

以下討論都在 \(\gcd(a,p)=1\) 的前提下.

1. 費馬小定理

此情況僅適用於 \(p\) 為質數的情況.

由費馬小定理知,當 \(p\) 為質數且 \(\gcd(a,p)=1\)

時有

\[a^{p-1}\equiv 1\pmod p \]

兩邊同除 \(a\)

\[a^{p-2}\equiv a^{-1}\pmod p \]

所以 \(inv(a)\) 其實就是 \(a^{p-2}\).

本題的答案就是 \(a\cdot b^{p-2}\).

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;

const int MOD = 19260817;

int read()
{
	int x = 0;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = ((x << 3) + (x << 1) + c - '0') % MOD;
		c = getchar();
	}
	return x;
}

ll ksm(int a, int b)
{
	ll base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = (ans * base) % MOD;
		}
		base = (base * base) % MOD;
		b >>= 1;
	}
	return ans;
}

int main()
{
	int a = read(), b = read();
	if (!b)
	{
		puts("Angry!");
		return 0;
	}
	printf("%lld\n", a * ksm(b, MOD - 2) % MOD);
	return 0;
}

2. \(\rm EXGCD\)

\(p\) 不為質數時 \(\rm EXGCD\) 也能求逆元.

要求出

\[a\cdot x\equiv 1\pmod p \]

相當於求出

\[ax+py=1 \]

的整數解.

注意:用 \(\rm EXGCD\) 求出來的可能是負數,要轉成正數.

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;

const int MOD = 19260817;

int read()
{
	int x = 0;
	char c = getchar();
	while (c < '0' || c > '9')
	{
		c = getchar();
	}
	while (c >= '0' && c <= '9')
	{
		x = ((x << 3) + (x << 1) + c - '0') % MOD;
		c = getchar();
	}
	return x;
}

ll x, y;

void exgcd(int a, int b)
{
	if (!b)
	{
		x = 1, y = 0;
		return;
	}
	exgcd(b, a % b);
	ll tmp = x;
	x = y;
	y = tmp - a / b * y;
}

int main()
{
	int a = read(), b = read();
	if (!b)
	{
		puts("Angry!");
		return 0;
	}
	exgcd(b, MOD);
	x = (x % MOD + MOD) % MOD;
	printf("%lld\n", a * x % MOD);
	return 0;
}

以上兩種演算法的時間複雜度均為 \(\mathcal{O}(\log n)\).

三、線性求 \(1\sim n\) 的逆元

P3811 【模板】乘法逆元

單個求時間為 \(\mathcal{O}(n\log n)\),會炸.

1. 遞推

首先 \(inv(1)=1\).

然後 \(p\) 可以表示成 \(iq+r\).

\[p\equiv0\pmod p\\ iq+r\equiv0\pmod p\\ r\equiv -iq\pmod p\\ r\cdot(i^{-1}r^{-1})\equiv-iq\cdot(i^{-1}r^{-1})\pmod p\\ i^{-1}\equiv-q\cdot r^{-1}\pmod p\\ i^{-1}\equiv-q\cdot(p\bmod i)^{-1}\pmod p\\ inv(i)\equiv-\left\lfloor\frac{p}{i}\right\rfloor\cdot inv(p\bmod i)\pmod p \]

然後就可以線性推了.

注意這樣算出來會有負數,要轉成正的.

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;

const int MAXN = 3e6 + 5;

int inv[MAXN];

signed main()
{
	int n, p;
	scanf("%lld%lld", &n, &p);
	inv[1] = 1;
	puts("1");
	for (int i = 2; i <= n; i++)
	{
		inv[i] = (-(p / i) * inv[p % i] % p + p) % p;
		printf("%lld\n", inv[i]);
	}
	return 0;
}

2. 階乘逆元

首先,在 \(\bmod\ p\) 意義下

\[inv((i+1)!)=((i+1)!)^{-1}\\ ((i+1)!)^{-1}\cdot(i+1)=(i!)^{-1}=inv(i!)\\ \therefore inv((i+1)!)\cdot(i+1)=inv(i!) \]

所以先 \(\mathcal{O}(\log n)\) 算出 \(inv(n!)\),就可以 \(\mathcal{O}(n)\) 一直推到 \(inv(1!)\) 了.

然後有

\[\begin{aligned} inv(i)&=i^{-1}\\ &=(i!)^{-1}\cdot (i-1)!\\ &=inv(i!)\cdot (i-1)! \end{aligned} \]

再用 \(\mathcal{O}(n)\) 算出 \(inv(1)\sim inv(n)\).

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;

const int MAXN = 3e6 + 5;

int n, p;
ll jc[MAXN], inv[MAXN];

ll ksm(int a, int b)
{
	ll base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = ans * base % p;
		}
		base = base * base % p;
		b >>= 1;
	}
	return ans;
}

int main()
{
	scanf("%d%d", &n, &p);
	jc[0] = jc[1] = 1;
	for (int i = 2; i <= n; i++)
	{
		jc[i] = jc[i - 1] * i % p;
	}
	inv[n] = ksm(jc[n], p - 2);
	for (int i = n - 1; i >= 1; i--)
	{
		inv[i] = inv[i + 1] * (i + 1) % p;
	}
	for (int i = 1; i <= n; i++)
	{
		printf("%lld\n", inv[i] * jc[i - 1] % p);
	}
	return 0;
}

以上兩種演算法的時間複雜度均為 \(\mathcal{O}(n)\).