乘法逆元
〇、前置
\(1.\) 整除符号:\(x\ |\ y\),表示 \(x\) 能被 \(y\) 整除,即 \(x\) 是 \(y\) 的因数。
\(2.\) 互质符号:\(x\ \bot\ y\),表示 \(x\) 与 \(y\) 互质。
\(3.\) 最大公约数:\(\gcd(m,n)\);最小公倍数:\(\operatorname{lcm}(m,n)\)。
\(4.\) 同余的定义:设整数 \(m\ne0\),若 \(m\ |\ (a-b)\),称 \(m\) 为模数,\(a\)同余于 \(b\) 模 \(m\),\(b\) 是 \(a\) 对模 \(m\) 的剩余。记作 \(a\equiv b\pmod{m}\)。
一、定义
如果一个线性同余方程 \(ax\equiv1\pmod{b}\),则称 \(x\) 为 \(a\ mod\ b\) 的逆元,记作 \(a^{-1}\)。即求逆元相当于求 \(\frac{1}{a}\ mod\ b\) 的值。
二、逆元求解方法
\(1.\) 扩展欧几里得法求逆元。
前提:满足 \(\gcd(a,b) = 1\)。
扩展欧几里得算法,常用于求解 \(ax+by=\gcd(a,b)\) 的一组可行解。
由于此时 \(ax+by=1\),所以 \(ax=1-by\),则 \(ax\ \%\ b=1\),所以此时 \(x\) 即为 \(ax\equiv1\pmod{b}\) 的解。
过程:详见 OI-wiki。(证明过程需要知道)
当最后求到两者的 \(gcd\) 的时候 \(ax+by=\gcd(a,b)=b\),所以最后一步的一个解就是 \(x=1,y=0\),然后往回递归即可,简要代码:
void Exgcd(ll a, ll b, ll &x, ll &y) {
if (!b) x = 1, y = 0;
else Exgcd(b, a % b, y, x), y -= a / b * x;
}
int main() {
ll x, y;
Exgcd (a, p, x, y);
x = (x % p + p) % p;
}
注:在 \(b\ne0\) 的情况下,\(|x|\le b\)(可能为负数)。
\(2.\) 费马小定理法求逆元。
费马小定理定义:若 \(p\) 为素数,\(\gcd(a,p)=1\),则 \(a^{p-1}\equiv1\pmod{p}\)。证明:详见 OI-wiki。(证明过程了解)
因为 \(ax\equiv1\pmod{b}\),所以 \(ax\equiv a^{b-1}\pmod{b}\)(根据费马小定理),所以 \(x\equiv a^{b-2}\pmod{b}\),然后利用快速幂求解即可。
ll ksm(int a,int b){
ll res = 1;
while(b){
if(b & 1) res = (ll) res * a % p;
a = (ll) a * a % p;
b >>= 1;
}
return res;
}
int main() {
ll x = (a, p - 2);
}
\(3.\) 线性求连续 \(n\) 个数的逆元。
即求出 \(1,2,…,n\) 中每个数关于 \(p\) 的逆元。
递归式:\(i^{-1}=\begin{cases}1,if\ i=1\\-\left\lfloor\dfrac{p}{i}\right\rfloor\ (p\ mod\ i)^{-1},otherwise\\\end{cases}\)\(\pmod{p}\)
证明过程:详见 OI-wiki。(证明过程需要知道)
时间复杂度:\(O(n)\)。
inv[1] = 1;
for (int i = 2; i <= n; ++i) inv[i] = (ll)(p - p / i) * inv[p % i] % p;
注:$p-\left\lfloor\dfrac{p}{i}\right\rfloor$ 是为了解决负数问题。
\(4.\) 线性求 \(n\) 个数的逆元。
即求出任意给定的 \(n\) 个数的逆元,记为 \(a_i\ (1\le i\le n,1\le a_i\le p)\)。
首先求出 \(n\) 个数的前缀积,记为 \(s_i\),然后使用扩欧或费马小定理方法求出 \(s_n\) 的逆元,记为 \(f_n\)。
因为 \(f_n\) 是前 \(n\) 个数的积的逆元,所以当它乘上 \(a_n\) 时,就会和 \(a_n\) 的逆元抵消,于是就得到了 \(a_1\) 到 \(a_{n-1}\) 的积的逆元,即 \(f_{n-1}\)。同理可以求出所有 \(f_i\)。
于是 \(a_i\) 的逆元,即 \(a_i^{-1}\) 就可以用 \(f_i\times s_{i-1}\) 求得。
时间复杂度:\(O(n+\log p)\)。
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
f[n] = ksm(s[n], p - 2);
for (int i = n; i >= 1; --i) f[i - 1] = f[i] * a[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = f[i] * s[i - 1] % p;
习题:
\(1.\) 扩展欧几里得求逆元模板题 P1082 [NOIP2012 提高组] 同余方程
清夜无尘,月色如银,何人叹隙中驹,石中火,梦中身。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
void Exgcd(ll a,ll b,ll &x,ll &y){
if(!b) return (void)(x = 1,y = 0);
Exgcd(b,a % b,y,x);
y -= a / b * x;
}
int main(){
ll a,b,x,y;
scanf("%lld%lld",&a,&b);
Exgcd(a,b,x,y);
x = (x % b + b) % b;
printf("%lld",x);
return 0;
}
\(2.\) 费马小定理求逆元模板题 P2613 【模板】有理数取余
此题注意两个点:
第一个,我们需要边输入变取模,我觉得一定有人不知道,是利用快读,然后一步一步实现,可以学习一下。其正确性显然。
第二个,在 \(b=0\) 的时候,无解。其正确性显然。
能见广阔天地之眼,必是低首俯察之目。
#include <bits/stdc++.h>
#define MOD 19260817
#define int long long
using namespace std;
int read(){
int x = 0;char ch = getchar();
while(ch < '0' or ch > '9') ch = getchar();
while(ch >= '0' and ch <= '9') x = ((x << 1) + (x << 3) + ch - 48) % MOD,ch = getchar();
return x;
}
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
b >>= 1;a = a * a % MOD;
}
return res;
}
signed main(){
int a = read(),b = read();
if(!b) return puts("Angry!"),0;
int c = a * ksm(b,MOD - 2) % MOD;
printf("%lld",c);return 0;
}
\(3.\) 线性求逆元模板题 P3811 【模板】模意义下的乘法逆元
回首看来路,山河忽已秋。
#include <bits/stdc++.h>
#define N 3000006
#define int long long
using namespace std;
int n,p,inv[N];
signed main(){
scanf("%lld%lld",&n,&p);
inv[1] = 1;puts("1");
for(int i = 2;i <= n;i ++) inv[i] = (p - p / i) * inv[p % i] % p,printf("%lld\n",inv[i]);
return 0;
}
\(4.\) 线性求 \(n\) 个数的逆元模板题 B3645 数列前缀和 2
夜来风雨,花落无算,竹笛清风,碧草横天。
#include <bits/stdc++.h>
#define N 1000006
#define P 1145141
#define int long long
using namespace std;
int n,Q,a[N],num,s[N],f[N];
void Input(){
scanf("%lld%lld",&n,&Q);
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
}
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % P;
b >>= 1;
a = a * a % P;
}
return res;
}
void work(){
s[0] = 1;for(int i = 1;i <= n;i ++) s[i] = s[i - 1] * a[i] % P;
f[n] = ksm(s[n],P - 2);
for(int i = n;i >= 1;i --) f[i - 1] = f[i] * a[i] % P;
int l,r;while(Q --){
scanf("%lld%lld",&l,&r);
num ^= (s[r] * f[l - 1] % P);
}
printf("%lld",num);
}
signed main(){
Input();
work();
return 0;
}
\(5.\) 通过逆元求组合数模板题 B3717 组合数问题
解析:\(C_n^m=\dfrac{n!}{(n-m)!\times m!}\),所以相当于求 \(n!\) 乘上 \(\ (n-m)!\times m!\) 在\(\pmod p\) 下的逆元。
静坐竹林白石,只觉漫天山色,皆来相就。
#include <bits/stdc++.h>
#define N 5000006
#define MOD 998244353
#define int long long
using namespace std;
int T,maxN,s[N],num;
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
b >>= 1;
a = a * a % MOD;
}
return res;
}
signed main(){
scanf("%lld%lld",&T,&maxN);
s[0] = 1;for(int i = 1;i <= maxN;i ++) s[i] = s[i - 1] * i % MOD;
int n,m;while(T --){
scanf("%lld%lld",&n,&m);
num ^= (s[n] * ksm(s[n - m] * s[m] % MOD,MOD - 2) % MOD);
}
printf("%lld",num);
return 0;
}
\(6.\) P2054 [AHOI2005] 洗牌
解析:通过模拟找规律的方法可以知道,对于初始位置为 \(x\) 的牌,在经过 \(1\) 次洗牌后,位置变为 \(x\times 2\pmod{n + 1}\),所以在经过 \(m\) 次洗牌后,位置变为 \(x\times 2^m\pmod{n+1}\),即 \(l\),题目要求求 \(x\),所以 \(x = \dfrac{l}{2^m}\pmod{n+1}\),由于 \(n+1\) 不一定为素数,所以需要利用扩展欧几里得求逆元的方法来求解。
肩上担清风,眉间无愁云。
#include <bits/stdc++.h>
#define int __int128
using namespace std;
inline int read()
{
int x = 0,f = 1;char ch = getchar();
while(ch < '0' or ch > '9'){
if (ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' and ch <= '9'){
x = x * 10 + ch - 48;
ch = getchar();
}
return x * f;
}
inline void write(int x)
{
if(x < 0){
putchar('-');
x = -x;
}
if(x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
int n,m,l,MOD;
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
b >>= 1;
a = a * a % MOD;
}
return res;
}
void Exgcd(int a,int b,int &x,int &y){
if(!b) return (void)(x = 1,y = 0);
Exgcd(b,a % b,y,x);
y -= a / b * x;
}
signed main(){
n = read(),m = read(),l = read();MOD = n + 1;
int a = ksm(2,m),x,y;
Exgcd(a,MOD,x,y);
x = (x % MOD + MOD) % MOD;
write(x * l % MOD);
return 0;
}
\(7.\) P4071 [SDOI2016] 排列计数
有 \(m\) 个位置为 \(i\) 的方案数好解决,为 \(C_n^m\) 个方案,关键在于剩余的 \(n - m\) 个位置为错排的方案数。
求错排的方案数可以参考 P1595 信封问题。
错排的递推式为 \(f_i=\begin{cases}0,if\ i=1\\1,if\ i = 2\\(i-1)\times (f_{i-1}+f_{i-2}),otherwise\\\end{cases}\)\(\pmod{P}\)
当然注意本题需要另外规定 \(f_0=1\)。
简单证明:将错排问题理解为,有 \(n\) 个颜色的球,\(n\) 个颜色的箱子,不能让一个球放进同一个颜色的箱子里,问有多少种方案。
假设我们将 \(n\) 号球放进了 \(k\) 号箱子。\((1\le k\le n-1)\)
如果此时我们把 \(k\) 号球放进了 \(n\) 号箱子,那么此时的方案数相当于剩下 \(n-2\) 个箱子的方案数,即 \(f_{n-2}\)。
如果此时我们不把 \(k\) 号球放进 \(n\) 号箱子,我们可以这样理解:\(k\) 号球不能与 \(n\) 号箱子匹配,其他除 \(k,n\) 之外的球不能与其对应颜色的箱子匹配,那么方案数相当于 \(n-1\) 个箱子的方案数,即 \(f_{n-1}\)。
由于 \(k\) 的取值个数为 \(n-1\) 个,所以 \(f_n=(n-1)\times (f_{n-1}+f_{n-2})\)。由此可以推得递推式。
那么本题最后结果为 \(C_n^m\times f_{n-m}\pmod{P}\)。
独对一方草木,更见光阴多情。
#include <bits/stdc++.h>
#define N 1000006
#define int long long
#define MOD 1000000007
using namespace std;
int T,n,m,s[N],f[N];
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
b >>= 1;
a = a * a % MOD;
}
return res;
}
signed main(){
s[0] = 1;for(int i = 1;i <= 1000000;i ++) s[i] = s[i - 1] * i % MOD;
f[0] = 1,f[1] = 0,f[2] = 1;for(int i = 3;i <= 1000000;i ++) f[i] = (i - 1) * (f[i - 1] + f[i - 2]) % MOD;
scanf("%lld",&T);while(T --){
scanf("%lld%lld",&n,&m);
int x = s[n] * ksm(s[n - m] * s[m] % MOD,MOD - 2) % MOD * f[n - m] % MOD;
printf("%lld\n",x);
}
return 0;
}
\(8.\) CF327C Magic Five
这个题我给出等比数列的求和公式,其他的自己去推。
对于 \(A=a+a\times b+a\times b^2+…+a\times b^n\) 求和,我们可以知道 \(bA=a\times b+a\times b^2+…+a\times b^{n+1}\),那么 \((b-1)A=a\times b^{n+1}-a\),所以 \(A=\dfrac{a\times b^{n+1}-a}{b-1}\)。
本题自己思考如何解决即可。如果不会可以去看题解(主要是这个题表述有点麻烦,但是确实容易想到怎么做,不要看它是一个蓝题,其实是一个水蓝)。
生有时,死有命,顺本心,当无憾。
#include <bits/stdc++.h>
#define int long long
#define MOD 1000000007
using namespace std;
string s;int k,ans;
void Input() {cin >> s;scanf("%lld",&k);}
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
void work(){
int l = s.size();
for(int i = 0;i < s.size();i ++){
if((int)(s[i] - '0') % 5 == 0){
int num1 = (ksm(2,i + l * k) + MOD - ksm(2,i)) % MOD;
int num2 = ksm(2,l) - 1;
ans = (ans + num1 * ksm(num2,MOD - 2) % MOD) % MOD;
}
}
}
void Output() {printf("%lld",ans);}
signed main(){
Input();
work();
Output();
return 0;
}

心在寰宇,自得自在,红尘万象,诸多烦恼。
浙公网安备 33010602011771号