【数学】逆元
一、逆元的意义
在一道题中,如果既有除法又有取余,那么是不能直接除的:
这时候逆元就登场了.
众所周知,\(a\cdot a^{-1}=1\),类似地定义 \(a\) 在模 \(p\) 意义下的逆元为使得
成立的 \(x\).
注意到当 \(\gcd(a,p)>1\) 时上式不可能成立,所以只有 \(a\) 与 \(p\) 互质时才有逆元.
那么在模 \(p\) 意义下,\(x=a^{-1}\),\(x\) 记作 \(inv(a)\).
考虑回刚刚的问题:
所以求出 \(inv(b)\),答案就是 \((a\cdot inv(b))\bmod p\).
二、求单个数的逆元
臭模数
用快读 \(+\) 取余读入.
首先 \(19260817\) 是个质数,所以当且仅当 \(19260817\mid b\) 时 \(b\) 没有逆元,即无解.
以下讨论都在 \(\gcd(a,p)=1\) 的前提下.
1. 费马小定理
此情况仅适用于 \(p\) 为质数的情况.
由费马小定理知,当 \(p\) 为质数且 \(\gcd(a,p)=1\) 时有
两边同除 \(a\) 得
所以 \(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;
}
ll inv(int a)
{
return ksm(a, MOD - 2);
}
int main()
{
int a = read(), b = read();
if (!b)
{
puts("Angry!");
return 0;
}
printf("%lld\n", a * inv(b) % MOD);
return 0;
}
2. \(\rm exgcd\)
当 \(p\) 不为质数时也能用 \(\rm exgcd\) 求逆元.
要求出
相当于求出
的整数解.
注意:用 \(\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;
}
ll inv(int a)
{
exgcd(a, MOD);
x = (x % MOD + MOD) % MOD;
return x;
}
int main()
{
int a = read(), b = read();
if (!b)
{
puts("Angry!");
return 0;
}
printf("%lld\n", a * inv(b) % MOD);
return 0;
}
以上两种算法的时间复杂度均为 \(\mathcal{O}(\log p)\).
当然根据欧拉定理有
这样在 \(p\) 不为质数时也能求,但你还得用 \(\mathcal{O}(\sqrt{p})\) 求出 \(\varphi(p)\),没有实际意义,
三、线性求 \(1\sim n\) 的逆元
单个求时间为 \(\mathcal{O}(n\log p)\),会炸.
1. 递推
首先 \(inv(1)=1\).
然后 \(p\) 可以表示成 \(iq+r\).
然后就可以线性推了.
注意这样算出来会有负数,要转成正的.
\(\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. 阶乘逆元
此方法需要保证 \(n<p\).
首先,在 \(\bmod\ p\) 意义下
所以先 \(\mathcal{O}(\log n)\) 算出 \(inv(n!)\),就可以 \(\mathcal{O}(n)\) 一直推到 \(inv(1!)\) 了.
然后有
再用 \(\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+\log p)\),且 \(p\) 必须为质数.
四、线性求 \(n\) 个数的逆元
思路基本同阶乘逆元.
预处理出前缀积 \(pro_i\).
那么
然后
时间复杂度为 \(\mathcal{O}(n+\log p)\),要卡卡常.
\(\text{Code}\)
#include <iostream>
#include <cstdio>
#define ll long long
using namespace std;
ll read()
{
ll x = 0;
char c = getchar();
while (c < '0' || c > '9')
{
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + c - '0';
c = getchar();
}
return x;
}
const int MAXN = 5e6 + 5;
ll n, p, k;
ll a[MAXN], pro[MAXN], inv[MAXN];
ll ksm(ll a, ll 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()
{
n = read(), p = read(), k = read();
pro[0] = 1;
for (int i = 1; i <= n; i++)
{
a[i] = read();
pro[i] = pro[i - 1] * a[i] % p;
}
inv[n] = ksm(pro[n], p - 2);
for (int i = n - 1; i >= 1; i--)
{
inv[i] = inv[i + 1] * a[i + 1] % p;
}
ll base = k, ans = 0;
pro[0] = 1;
for (int i = 1; i <= n; i++)
{
ll x = inv[i] * pro[i - 1] % p;
ans = (ans + base * x % p) % p;
base = base * k % p;
}
printf("%lld\n", ans);
return 0;
}