乘法逆元及两道模板题详解

乘法逆元

就是a\times b\equiv 1(\mod p\left\left\left)

此时b就是a模p意义下的逆元,即inv[a]=b;

下面我们用inv[a]表示a模p意义下的逆元。

逆元是好东西啊

有时候我们需要算出 a/b mod p 的值,用朴素的方法,我们只能在 a 上不断加 p ,直到它能被 b 整除为止。
当 a,b,p 都很大的时候,这种方法就只能凉凉了,但如果有了逆元,我们就可以非常方便,快捷地求解。                    
 
                                                                      ——————某位大佬的话

所以我先讲讲逆元性质:

唯一性就不用讲了

1.积性

假如a与b互质,inv[a]\times inv[b]\equiv inv[a*b]

2.乘变除

a\times inv[b]\equiv a\div b (\mod p\left\left) 

证明如下:

b\times inv[b]\equiv 1\left ( \mod p \left\left)          

两边都乘一个 \frac{a}{b} ,就得到了上面的式子


至于逆元的求法,有很多,我只讲四种。

首先,是求单个逆元

1.费马小定理

当 p 为素数时:

a^{p-1}\equiv 1(\mod p\left\left\left)

a^{p-1}= a\times a^{p-2}

所以

a\times a^{p-2}\equiv 1(\mod p\left\left\left)

所以 a^{p-2}就是a在模p意义下的逆元,快速幂求出 a^{p-2}即可。

此方法的局限就是p只能是质数。

 1 int ksm(int t)
 2 {
 3     if(t==1)return n%p;
 4     if(t%2==0)
 5     return ksm(t>>1)*ksm(t>>1)%p;
 6     else
 7     return ksm(t>>1)*ksm(t>>1)*(n)%p;
 8 }
 9 int main()
10 {
11     scanf("%d%d",&n,&p);
12     printf("%d",ksm(p-2));
13 }

 

2.扩展欧几里得

a\times inv[a]\equiv 1(\mod p\left\left\left)

变一下,我们把p移到左边就成了

a\times inv[a]-py= 1

因为y是变量所以可以变成这样:

a\times inv[a]+py= 1

我们把inv[a]设成x,就变成了扩欧的标准式:

ax+py= 1

就可以瞎搞了,至于不知道怎么瞎搞的同学,可以先点这里exgcd入门以及同余基础

 1 inline void exgcd(ll a,ll b)
 2 {
 3     if(!b)
 4     {x=1;y=0;return;}
 5     exgcd(b,a%b);
 6     k=x;x=y;
 7     y=k-a/b*y;
 8     return;
 9 }
10 int main()
11 {
12     scanf("%d%d",&n,&p);
13     exgcd(n,p);
14     printf("%d",x);
15 }

然后,就是求多个逆元

1.欧拉函数筛法

在数论,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目(φ(1)=1)。
                                                         ——————百度百科

我们设\varphi(x)表示小于等于x且与x互质的正整数的个数。

通式如下:

还有欧拉函数有如下几点性质:

  1. 欧拉函数是积性函数——若m,n互质,
  2. 假如n为奇数,
  3. 假如n为质数,

欧拉函数与费马小定理有点不明不白的关系

对任何两个互质的正整数a, m(m>=2)有

这就是欧拉定理

所以类比于费马小定理的证法:

a^{\phi\left (m \right )}\equiv 1(\mod p\left\left\left)

a^{\phi\left (m \right )}= a\times a^{\phi\left (m \right )-1}

所以a\times a^{\phi(m)-1} \equiv 1(\mod p\left\left\left)

所以我们要求的就是a^{\phi(m)-1},又因为欧拉函数是积性函数,所以把m筛一下质数就好了。

不过有更好的办法,这里就不写代码了(其实不会

2.线性递推

证明好麻烦啊,那就不证了

反正就是设p= b\times k+a

再往a\times inv[a]\equiv 1(\mod p\left\left\left)

最后得出:-(\frac{p}{a})*inv[p\mod a]\equiv inv[a]

 


讲完了,现在讲题目

我要讲洛谷的两道题:

1.P3811

这就是模板题,直接套线性递推,就能ac了

 1 int main()
 2 {
 3     int n,m;
 4     read(n),read(m);
 5     inv[1]=1;
 6     for(register int i=2;i<=n;i++)
 7     inv[i]=(long long)(m-m/i)*inv[m%i]%m;\\加一个m是为了去负
 8     for(register int i=1;i<=n;i++)
 9     printf("%d\n",inv[i]);
10 }

  值得一提的是,这题扩欧卡一卡也可以过(好水

 1 long long k,x,y;
 2 inline void read(long long &x)
 3 {
 4     x=0; 
 5     int f=1; 
 6     char ch=getchar();
 7     while(ch<'0'||ch>'9')
 8     {
 9         if(ch=='-') 
10         f=-1; 
11         ch=getchar();
12     }
13     while(ch>='0'&&ch<='9')
14     {
15         x=x*10+ch-'0';
16         ch=getchar();
17     }x*=f;
18 }
19 inline void exgcd(int a,long long b)
20 {
21     if(b==0)
22     {x=1;y=0;return;}
23     exgcd(b,a%b);
24     k=x;x=y;
25     y=k-a/b*y;
26     return;
27 }
28 int main()
29 {
30     long long n,m;
31     read(n),read(m);
32     for(register int i=1;i<=n;i++)
33     {
34         exgcd(i,m);
35         if(x<=0)x+=m;
36         printf("%lld\n",x);
37     }
38 }

对了,还有一个,费马小定理算法。

得分48,a了3个,t了3个

 1 inline long long ksm(int t,int i)//记得开long long不然第三个点会wa
 2 {
 3     if(t==1)return i%m;
 4     if(t%2==0)
 5     return ksm(t>>1,i)*ksm(t>>1,i)%m;
 6     else
 7     return ksm(t>>1,i)*ksm(t>>1,i)*(i)%m;
 8 }
 9 int main()
10 {
11     long long n;
12     io::begin();
13     io::read(n);
14     io::read(m);
15     for(register int i=1;i<=n;i++)
16     printf("%lld\n",ksm(m-2,i));
17 }

但是我发现,去掉 inline,就a了4个点,得分64.

所以给大家一个忠告,在递归函数下,inline不是一个好的卡常方法!

因为使用内联函数后虽然调用函数的开销降低了,但是有利必有弊,内联函数会导致主函数指令增多、函数体积增大等情况。

这题就这样

2.P2613

一看数据范围还是很吓人的,10^{10001},怕不是要打高精,后来发现不是这样,不过题解中真有高精算法,大佬!!

首先,先看我最开始引用的话,嗯,有点道理

\frac{a}{b}=a\times b^{-1}

然后解析题目发现:本题就是求b在mod p的意义下的逆元。

所以扩欧一遍过,只是读入时注意一下边读边模就好了;

 1 inline void exgcd(int a,int b)
 2 {
 3     if(!b)
 4     {x=1;y=0;pos=a;return;}
 5     exgcd(b,a%b);
 6     k=x;x=y;
 7     y=k-a/b*y;
 8     return;
 9 }
10 int main()
11 {
12     std::cin>>a1>>b1;
13     int n=0,m=0;
14     for(register int i=0;i<strlen(a1);i++)
15     n=(n*10+a1[i]-48)%mod;
16     for(register int i=0;i<strlen(b1);i++)
17     m=(m*10+b1[i]-48)%mod;
18     exgcd(m,mod);
19     if(pos!=1)printf("Angry!");
20     else
21     {
22         if(x<0)x+=mod;
23         printf("%lld",(1ll*n*x)%mod);
24     }
25 }

最后通过这道题的大质数给大家念一首诗:苟利国家生死以

posted @ 2018-08-23 21:18  __mashiro  阅读(2117)  评论(0编辑  收藏  举报