乘法逆元(费马小定理,拓欧,线性dp)

1,定义

我们取余算法是没有除法取余的操作的(a/b)%p!=(a%mod/b%mod)%p.

因此我们乘法逆元的目的就是寻找一个整数x,使得(ax)%p等价于(a*1/b)%p.

我们称之为全等于≡,即ax=a*1/b(mod p).我们这个x的意义就与1/b一样。(即xb≡1(mod p))。

我们一般有3种求逆元的方法:费马小定理,拓欧,线性dp。

2,费马小定理求逆元

费马小定理:如果p为素数(这是前提)并且gcd(a,p)=1,那么a^(p-1)≡1(mod p)---->a^(p-2)*a≡1(mod p),所以a^(p-2)就是a的逆元。

证明如下:

1,因为p是素数,毫无疑问:a[1]=1~a[p-1]=p-1都与p互质。我们称a[1]~a[p-1]是p的一个完全剩余系。因为a,p互质。所以a*a[j]%p得到的数也肯定是1到p-1之间(不可能是0)。

2,我们保证容易  a*a[i]   !≡  a*a[j]  (mod p)  .  (i!=j)   

      证明如下:假设两边全等于,那么(a[i]-a[j])*a≡0(mod p).因为a,p互质,毫无疑问可以消去          a,最后变为a[i]≡a[j](mod p),显然不成立。

     所以我们得出a*a[1]~a*a[p-1]也是p的一个完全剩余系。那么                                                             a[1]*a[2]*....*a[p-1]  ≡(a*a[1])*...* (a*a[p-1])  (mod p)消去a[1]*..*a[p-1],就得到a^(p-1)≡1(mod p).

3,所以如果给定的a,p互质且p为质素,我们可以直接用快速求a^(p-2)(mod p)就是a的逆元了。

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

int32_t main()
{
	cin >> a >> mod;
	cout << fastmi(a, mod - 2) << endl;//逆元是a^(p-2) (mod p)
	return 0;
}

3,拓展欧几里得(exgcd)

先说求求逆元的应用:当gcd(a,p)=1,不要求p为素数,a的逆元就是ax+by=1的x。

  1. 我们首先证明ax+by=c有解的前提是c是gcd(a,b)整数倍   (裴蜀定理/贝祖定理)     

    还是反证法:假设c不是d=gcd(x,y)的整数倍,设ax=x*k1*d,by=y*k2*d,c=kd+h(其中k1与k2互质且0<h<d)。                                                                                                                       那么原式<==>xk1d+yk2d=kd+h,两边同除于d,得到整数==整数+分数(显然不成立)

  2. 求逆元ax≡1(mod p)我们可以看出ax+py≡1=gcd(a,p)(mod p),可以补上py显然是py%p=0,y为任意解(无所谓,我只要x)

  3. 拓展欧几里得就是求当gcd(a,b)=1时,ax+by的解x,y。

    欧几里得算法(gcd)辗转最后就是gcd(a,b)=a,b=0,此时x=1,y=0.由最终确定的x,y我们是可以一步步退回辗转过的a,b对于的解x,y,那么我们需要知道辗转时x1,y1与x2,y2的关系。

  4. ax1+by1==1==bx2+(a%b)y2,(因为a%b==a-(a/b)*b)),所以a*(x1-y2)+b(y1-x2+(a/b)*y2)==0   解得x1=y2,y1=x2-(a/b)*y2。

int a, mod;
void exgcd(int a, int b, int &x, int &y)//&直接修改值
{
	if (!b)x = 1, y = 0;
	else
		{
			exgcd(b, a % b, y, x);//这里替换回来后,x就继承了深层的y,y就继承深层的x,那么x不用修改,y再减去(A/B)*y2即-(a/b)*x即可
			y -= (a / b) * x;
		}
}

int32_t main()
{
	cin >> a >> mod;
	int x, y;
	exgcd(a, mod, x, y);
	x = ( x + mod) % mod;//保证逆元为正数
	printf("%lld\n", x);
	return 0;
}

 4,线性dp

如果只是求单个数的逆元,显然上面两种方法是足够的,但是如果求1~n的逆元,上面的复杂度就是O(n*logn),有点小高。

  1. 当我们求i的逆元时,尝试使用i之前求出的逆元。
  2. 令k=p/i,j=p%i  ,  显然p=k*i+j。建立了i与p与其他i的关系。可以得出k*i+j≡0(mod p),我们的目标是化出i^(-1)的形式,所以我们两边同乘i^(-1),j^(-1),即k*j^(-1)+i^(-1)≡0(mod p)。即i^(-1)≡-[p/i]*(p%i)^(-1)   (mod p)。
  3. 因为p%i<i,所以(p%i)的逆元我们已经求出
cin >> n >> mod;
	inv[1] = 1;
	for (int i = 2; i <= n; ++i)inv[i] = (mod - mod / i) * inv[mod % i] % mod;//(mod-mod/i)是为了保证逆元为正数,反正mod p加上p也不影响

模板题:P3811 【模板】乘法逆元

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
const int N = 3e6 + 10;
int n, mod;
ll inv[N];
int32_t main()
{
	cin >> n >> mod;
	inv[1] = 1;
	for (int i = 2; i <= n; ++i)inv[i] = (mod - mod / i) * inv[mod % i] % mod;//(mod-mod/i)是为了保证逆元为正数,反正mod p加上p也不影响
	for (int i = 1; i <= n; ++i)printf("%lld\n", inv[i]);//毒瘤题不让我用cout
	return 0;
}

5,线性dp拓展

通过观察逆元,我们发现即使求的n个数不是连续的,我们也可以递推得出n个数各自的逆元。

假设我们n个数积一起,那他们的积逆元sv[n]<==>1/(a[1]*a[2]*...*a[n]),如果我们sv[n]*a[n],会发现得到积逆元sv[n-1]。

我们得出求逆元inv[i],那么只需要sv[i]*s[i-1](即i的积逆元*前i-1个数的积就可以得到。

因此,我们只需要求sv[n]就可以递推全部。

模板题:P5431 【模板】乘法逆元 2

#include <bits/stdc++.h>
using namespace std;
#define ll     long long
#define int ll
const int N = 5e6 + 100;

int sv[N], s[N], a[N];
int n, mod, k, ki = 1,ans;
inline ll read(ll &x)
{
	x = 0;
	char ch = 0;
	while (ch < '0' || ch > '9')ch = getchar();
	while (ch >= '0' && ch <= '9')
		{
			x = x * 10 + ch - '0';
			ch = getchar();
		}
	return x;
}

ll fastmi(ll base, ll power)
{
	ll ans = 1;
	while (power)
		{
			if (power & 1)ans = (ans * base) % mod;
			power >>= 1;
			base = (base * base) % mod;
		}
	return ans;
}

int32_t main()
{
	read(n), read(mod), read(k);
	s[0] = 1;
	for (int i = 1; i <= n; ++i)read(a[i]), s[i] = s[i - 1] * a[i] % mod;//乘法记得mod
	sv[n] = fastmi(s[n], mod - 2);
	for (int i = n - 1; i >= 1; --i)sv[i] = sv[i + 1] * a[i + 1] % mod;//求积逆元
	for (int i = 1; i <= n; ++i)
		{
			ki = (ki * k) % mod;
			int	inv = sv[i] * s[i - 1] % mod ;//这勾题卡快读就算了,还卡这种常数,开inv数组没有必要,而且还会mle,用inv暂时存储inv[i]即可
			ans = (ans + inv * ki % mod) % mod;
		}
	cout << ans << endl;
	return 0;
}

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值