入门算法之乘法逆元与拓展欧几里得与欧拉函数(面向新生)讲解及其各种求法

      逆元在咱们做题中出现的频率还是很高的,往往用来求解分数取余中,例如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

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值