逆元在咱们做题中出现的频率还是很高的,往往用来求解分数取余中,例如cnm(组合数),而且可以求解1e6之内的组合数%p;
首先看一下百度百科的解释:
乘法逆元,是指数学领域群G中任意一个元素a,都在G中有唯一的逆元a',具有性质a×a'=a'×a=e,其中e为该群的单位元。
通俗一点,就是定义在一种运算里面的数都存在一个唯一的逆元,使得a×a的逆元为单位元,我们这里主要说的运算也就是取余预算mod了,这个单位元e呢,就是对于任意元素a,e运算上a=a,a运算上e也等于a;乘法运算中,1就是这个单位元,所以a×a的逆元%p=1;
下面给出最常见的一种快速幂的解法:要求a,p互质。
先给出欧拉定理:若a,p互质,则 pow(a,oula(p))%p同余1,就是a的oula(p)次方%p=1;oula(p)=从1到p与p互质的个数。
那求解就很简单了,a的逆元就等于pow(a,oula(a)-1)%p;快速幂求解即可了。当然如果p不是素数的话我们可以用欧拉线性筛求解欧拉函数,或者p过大采用唯一分解定理oula(p)。
ll qpow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1) ans=(ans*a)%mod;
a=(a*a)%mod;
b>>=1;
}
return ans;
}
a的逆= printf("%lld\n",qpow(a,oula(mod-1))); 如果p为质数就是传mod-2呗
然后在这里顺便把欧拉筛和欧拉函数也说了吧(基于欧拉筛)线性复杂度,当然如果n过大,到1e14的话,可以筛出1e7的素数,利用唯一分解定理求解因子数来求解oula[n],
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#pragma G++ optimize(3)
const int maxx=1e6+8;
const ll mod=998244353;
ll dp[maxx][10];
ll sushu[maxx],biaoji[maxx],cnt=0,oula[maxx];
void shai()
{
for(int i=2;i<maxx;i++)
{
if(!biaoji[i])
{
sushu[++cnt]=i;
oula[i]=i-1;
}
for(int j=1;j<=cnt&&i*sushu[j]<maxx;j++)
{
biaoji[i*sushu[j]]=1;
// 每次循环下来的时候oula[i] 已经求得.
if(i%sushu[j]==0)// 如果i是素数的倍数 保证这个数只被筛一次 i*sushu[j]中有sushu[j]块i,一块里面oula[i]个 *sushu[j]
{
oula[i*sushu[j]]=oula[i]*sushu[j];
break;
}
else // i和sushu[j] 互质:
oula[i*sushu[j]]=(oula[sushu[j]])*oula[i]; // =(sushu[j]-1*oula[i])
}
}
}
int main()
{
ll t,m,i,j,k,sum=0,p,n;
shai();
for(i=1;i<=100;i++) printf("%lld\n",oula[i]);
return 0;
}
、、、区间筛、、、
再给出一个线性推逆元的方法:1的逆=1;
令p=k*i+r; r<i<p
(k*i+r)%p=0;
(k×r'+i')%p=0;
i'=-k*r'
=-p/i*(p%i)'
因为p%i<i;所以可以根据前面的逆元求解当前的逆元;
A[1]=1; A[i] = ( p - p / i) * A[p % i] % p(i>=2);
下面说一下拓欧,ax+by=gcd(a,b),
ax1+by1=gcd(a,b)
//bx2+(a%b)y2=gcd(b,a%b)
bx2+(a-b*a/b)y2=gcd(b,a%b)
提出a,b
ax1+by1=ay2+b(x2-a/b*y2)
x1 = y2(等式两边a的系数相同)
y1 = x2 - (a / b) * y2 (等式两边b的系数相同)
a*1+b*0=a gcd() 当b=0 return a
所以说最后一个解为x=1 y=0 b=0 a = gcd(a,b)
往上回溯即可。
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
using namespace std;
//int a[10000],b[10000];
int x,y,t; // 全局
int ojld(int a,int &x,int b,int &y) // &x在函数形参中也可改变;
{
if (b==0)
{
x=1,y=0;
return a;
}
t=ojld(b,y,a%b,x); // 递推公式; x1=y2;y1=x2-a/b*y2;此时将x,y互换位置 x=原来的y y变为此时的x=y=y-a/b*x;
y-=a/b*x;
return t;
}
int main()
{
int i,j,k,m,n,sum,count;
int a,b;
scanf("%d%d",&a,&b);
t=ojld(a,x,b,y);
printf("%d\n",t);
printf("%d %d\n",x,y);
return 0;
}
当然这玩意也能用来求%p的逆元吧
int ojldd(int a,int &x,int b,int &y)
{
if(b==0)
{
x=1;y=0;return a;
}
else
{
int t=ojld(b,x,a%b,y);
y-=a/b*x;
return t;
}
}
int inv(int a)
{
int x,y,b=mod;
exgcd(a,b,x,y);
if (x<0) x+=mod;
return x;
}
当然此时求出来的解并不一定是正整数解,但是可以求出此解系: ax+by=gcd(a,b) 已经成立,则a(x+b/gcd)+b(y-a/gcd) =gcd 则可求解最小满足的正整数解为:(x%gcd+b/gcd)%gcd。
下面考虑一下ax+by=n 在什么情况下有解,以知n为gcd(a,b) 倍数时一定有解。 else 一定无解,因为如果n不是a和b最大公因子的倍数的话,n一定不能被x倍的a和y倍的b表示,因为a*x与b*y的插值一定是gcd(a,b)的倍数,所以ax+by只能组成出gcd(a,b)倍数的数来。
推荐练习题:
https://vjudge.net/contest/351852#overview
密码lduicpc