乘法逆元

〇、前置

\(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;
}
posted @ 2023-10-31 20:07  Joy_Dream_Glory  阅读(65)  评论(0)    收藏  举报