乘法逆元(转)

转自我敬爱的大神:ACdreamers(http://blog.csdn.net/acdreamers/article/details/8220787)

今天我们来探讨逆元在ACM-ICPC竞赛中的应用,逆元是一个很重要的概念,必须学会使用它。

对于正整数,如果有,那么把这个同余方程中的最小正整数解叫做的逆元。

逆元一般用扩展欧几里得算法来求得,如果为素数,那么还可以根据费马小定理得到逆元为

推导过程如下

求现在来看一个逆元最常见问题,求如下表达式的值(已知

当然这个经典的问题有很多方法,最常见的就是扩展欧几里得,如果是素数,还可以用费马小定理。

但是你会发现费马小定理和扩展欧几里得算法求逆元是有局限性的,它们都会要求互素。实际上我们还有一

种通用的求逆元方法,适合所有情况。公式如下

现在我们来证明它,已知,证明步骤如下

接下来来实战一下,看几个关于逆元的题目。

题目:http://poj.org/problem?id=1845

题意:给定两个正整数,求的所有因子和对9901取余后的值。

分析:很容易知道,先把分解得到,那么得到,那么

的所有因子和的表达式如下

所以我们有两种做法。第一种做法是二分求等比数列之和。

代码:

[cpp] view plain copy

  1. #include <iostream>
  2. #include <string.h>
  3. #include <stdio.h>
  4. using namespace std;
  5. typedef long long LL;
  6. const int N = 10005;
  7. const int MOD = 9901;
  8. bool prime[N];
  9. int p[N];
  10. int cnt;
  11. void isprime()
  12. {
  13. cnt = 0;
  14. memset(prime,true,sizeof(prime));
  15. for(int i=2; i<N; i++)
  16. {
  17. if(prime[i])
  18. {
  19. p[cnt++] = i;
  20. for(int j=i+i; j<N; j+=i)
  21. prime[j] = false;
  22. }
  23. }
  24. }
  25. LL power(LL a,LL b)
  26. {
  27. LL ans = 1;
  28. a %= MOD;
  29. while(b)
  30. {
  31. if(b & 1)
  32. {
  33. ans = ans * a % MOD;
  34. b--;
  35. }
  36. b >>= 1;
  37. a = a * a % MOD;
  38. }
  39. return ans;
  40. }
  41. LL sum(LL a,LL n)
  42. {
  43. if(n == 0) return 1;
  44. LL t = sum(a,(n-1)/2);
  45. if(n & 1)
  46. {
  47. LL cur = power(a,(n+1)/2);
  48. t = (t + t % MOD * cur % MOD) % MOD;
  49. }
  50. else
  51. {
  52. LL cur = power(a,(n+1)/2);
  53. t = (t + t % MOD * cur % MOD) % MOD;
  54. t = (t + power(a,n)) % MOD;
  55. }
  56. return t;
  57. }
  58. void Solve(LL A,LL B)
  59. {
  60. LL ans = 1;
  61. for(int i=0; p[i]*p[i] <= A; i++)
  62. {
  63. if(A % p[i] == 0)
  64. {
  65. int num = 0;
  66. while(A % p[i] == 0)
  67. {
  68. num++;
  69. A /= p[i];
  70. }
  71. ans *= sum(p[i],num*B) % MOD;
  72. ans %= MOD;
  73. }
  74. }
  75. if(A > 1)
  76. {
  77. ans *= sum(A,B) % MOD;
  78. ans %= MOD;
  79. }
  80. cout<<ans<<endl;
  81. }
  82. int main()
  83. {
  84. LL A,B;
  85. isprime();
  86. while(cin>>A>>B)
  87. Solve(A,B);
  88. return 0;
  89. }

第二种方法就是用等比数列求和公式,但是要用逆元。用如下公式即可

因为可能会很大,超过int范围,所以在快速幂时要二分乘法。

代码:

[cpp] view plain copy

  1. #include <iostream>
  2. #include <string.h>
  3. #include <stdio.h>
  4. using namespace std;
  5. typedef long long LL;
  6. const int N = 10005;
  7. const int MOD = 9901;
  8. bool prime[N];
  9. int p[N];
  10. int cnt;
  11. void isprime()
  12. {
  13. cnt = 0;
  14. memset(prime,true,sizeof(prime));
  15. for(int i=2; i<N; i++)
  16. {
  17. if(prime[i])
  18. {
  19. p[cnt++] = i;
  20. for(int j=i+i; j<N; j+=i)
  21. prime[j] = false;
  22. }
  23. }
  24. }
  25. LL multi(LL a,LL b,LL m)
  26. {
  27. LL ans = 0;
  28. a %= m;
  29. while(b)
  30. {
  31. if(b & 1)
  32. {
  33. ans = (ans + a) % m;
  34. b--;
  35. }
  36. b >>= 1;
  37. a = (a + a) % m;
  38. }
  39. return ans;
  40. }
  41. LL quick_mod(LL a,LL b,LL m)
  42. {
  43. LL ans = 1;
  44. a %= m;
  45. while(b)
  46. {
  47. if(b & 1)
  48. {
  49. ans = multi(ans,a,m);
  50. b--;
  51. }
  52. b >>= 1;
  53. a = multi(a,a,m);
  54. }
  55. return ans;
  56. }
  57. void Solve(LL A,LL B)
  58. {
  59. LL ans = 1;
  60. for(int i=0; p[i]*p[i] <= A; i++)
  61. {
  62. if(A % p[i] == 0)
  63. {
  64. int num = 0;
  65. while(A % p[i] == 0)
  66. {
  67. num++;
  68. A /= p[i];
  69. }
  70. LL M = (p[i] - 1) * MOD;
  71. ans *= (quick_mod(p[i],num*B+1,M) + M - 1) / (p[i] - 1);
  72. ans %= MOD;
  73. }
  74. }
  75. if(A > 1)
  76. {
  77. LL M = MOD * (A - 1);
  78. ans *= (quick_mod(A,B+1,M) + M - 1) / (A - 1);
  79. ans %= MOD;
  80. }
  81. cout<<ans<<endl;
  82. }
  83. int main()
  84. {
  85. LL A,B;
  86. isprime();
  87. while(cin>>A>>B)
  88. Solve(A,B);
  89. return 0;
  90. }

其实有些题需要用到的所有逆元,这里为奇质数。那么如果用快速幂求时间复杂度为

如果对于一个1000000级别的素数,这样做的时间复杂度是很高了。实际上有的算法,有一个递推式如下

它的推导过程如下,设,那么

对上式两边同时除,进一步得到

再把替换掉,最终得到

初始化,这样就可以通过递推法求出模奇素数的所有逆元了。

另外的所有逆元值对应中所有的数,比如,那么对应的逆元是

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2186

题意:互质的数的个数,其中

分析:因为,所以,我们很容易知道如下结论

   对于两个正整数,如果的倍数,那么中与互素的数的个数为

     本结论是很好证明的,因为中与互素的个数为,又知道,所以

结论成立。那么对于本题,答案就是

其中为小于等于的所有素数,先筛选出来即可。由于最终答案对一个质数取模,所以要用逆元,这里

求逆元就有技巧了,用刚刚介绍的递推法预处理,否则会TLE的。

代码:

[cpp] view plain copy

  1. #include <iostream>
  2. #include <string.h>
  3. #include <stdio.h>
  4. #include <bitset>
  5. using namespace std;
  6. typedef long long LL;
  7. const int N = 10000005;
  8. bitset<N> prime;
  9. void isprime()
  10. {
  11. prime.set();
  12. for(int i=2; i<N; i++)
  13. {
  14. if(prime[i])
  15. {
  16. for(int j=i+i; j<N; j+=i)
  17. prime[j] = false;
  18. }
  19. }
  20. }
  21. LL ans1[N],ans2[N];
  22. LL inv[N];
  23. int main()
  24. {
  25. isprime();
  26. int MOD,m,n,T;
  27. scanf("%d%d",&T,&MOD);
  28. ans1[0] = 1;
  29. for(int i=1; i<N; i++)
  30. ans1[i] = ans1[i-1] * i % MOD;
  31. inv[1] = 1;
  32. for(int i=2;i<N;i++)
  33. {
  34. if(i >= MOD) break;
  35. inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
  36. }
  37. ans2[1] = 1;
  38. for(int i=2; i<N; i++)
  39. {
  40. if(prime[i])
  41. {
  42. ans2[i] = ans2[i-1] * (i - 1) % MOD;
  43. ans2[i] = ans2[i] * inv[i % MOD] % MOD;
  44. }
  45. else
  46. {
  47. ans2[i] = ans2[i-1];
  48. }
  49. }
  50. while(T--)
  51. {
  52. scanf("%d%d",&n,&m);
  53. LL ans = ans1[n] * ans2[m] % MOD;
  54. printf("%lld\n",ans);
  55. }
  56. return 0;
  57. }

接下来还有一个关于逆元的有意思的题目,描述如下

证明:

其中

所以只需要证明,而我们知道的逆元对应全部

中的所有数,既是单射也是满射。

所以进一步得到

证明完毕!

时间: 2024-10-13 01:43:17

乘法逆元(转)的相关文章

HDU3037 Saving Beans(Lucas定理+乘法逆元)

题目大概问小于等于m个的物品放到n个地方有几种方法. 即解这个n元一次方程的非负整数解的个数$x_1+x_2+x_3+\dots+x_n=y$,其中0<=y<=m. 这个方程的非负整数解个数是个经典问题,可以+1转化正整数解的个数用插板法解决:$C_{y+n-1}^{n-1}=C_{y+n-1}^y$. 而0<=y<=m,最后的结果就是—— $$\sum_{i=0}^m C_{i+n-1}^i$$ $$C_{n-1}^0+C_{n}^1+C_{n+1}^2+\dots+C_{n-1

UVa 11174 (乘法逆元) Stand in a Line

题意: 有n个人排队,要求每个人不能排在自己父亲的前面(如果有的话),求所有的排队方案数模1e9+7的值. 分析: <训练指南>上分析得挺清楚的,把公式贴一下吧: 设f(i)为以i为根节点的子树的排列方法,s(i)表示以i为根的子树的节点总数. f(i) = f(c1)f(c2)...f(ck)×(s(i)-1)!/(s(c1)!s(c2)!...s(ck)!) 按照书上最开始举的例子,其实这个式子也不难理解,就是先给这些子树确定一下位置,即有重元素的全排列. 子树的位置确定好以后,然后再确定

LightOJ - 1050 (唯一分解+推公式+乘法逆元)

题意:求a^b的所有约数和对1e9+7取模的结果 思路:对于一个数p,进行唯一分解,则p=P1^M1*P2^M2*...*Pn^Mn,则p的所有约数之和等于(P1^0+P1^1+...+P1^M1)*(P2^0+P2^1+...+P2^M2)*...*(Pn^0+Pn^1+...+Pn^Mn), p^t=P1^(M1*t)*P2^(M2*t)*...*Pn^(Mn*t),每一个(Pn^0+Pn^1+...+Pn^Mn)利用等比数列可以直接推出公式为(Pn^(Mn*t+1)-1)/(Pn-1),用

【奇技淫巧】数学技巧之乘法逆元

一.写在前面 开始码这篇blog之前我就意识到,这篇blog将会是我到目前为止码出的所有blog中最水的一篇.说是讲乘法逆元,但蒟蒻博主自己都不会证明_(:з」∠)_所以只打算放一个说明书式的用法,还请诸位看官老爷轻喷. 二.关于乘法逆元 我们知道模法交配率(Magic Coitus Law)(←_←其实并没有这东西)并不适用于除法.用式子表示大概是这样的: 但乘法逆元(Multiplicative Inverse Modulo,下式中用a'表示)就可以完成这样一个奇妙的操作: 然后我们就能用模

乘法逆元...Orz

最近打的几场比赛,都出现了有关逆元的题目,今天就整理了一下... 求乘法逆元的几种方法:http://www.cnblogs.com/james47/p/3871782.html 博文转载链接:http://blog.csdn.net/acdreamers/article/details/8220787 今天我们来探讨逆元在ACM-ICPC竞赛中的应用,逆元是一个很重要的概念,必须学会使用它. 对于正整数和,如果有,那么把这个同余方程中的最小正整数解叫做模的逆元. 逆元一般用扩展欧几里得算法来求

Codeforces 543D Road Improvement(树形DP+乘法逆元)

题目大概说给一棵树,树的边一开始都是损坏的,要修复一些边,修复完后要满足各个点到根的路径上最多只有一条坏的边,现在以各个点为根分别求出修复边的方案数,其结果模1000000007. 不难联想到这题和HDU2196是一种类型的树形DP,因为它们都要分别求各个点的答案.然后解法也不难想: dp0[u]表示只考虑以u结点为根的子树的方案数 dp1[u]表示u结点往上走,倒过来,以它父亲为根那部分的方案数 有了这两部分的结果,对于各个点u的答案就是dp0[u]*(dp1[u]+1).这两部分求法如下,画

乘法逆元(转)

定义:满足a*k≡1 (mod p)的k值就是a关于p的乘法逆元. 为什么要有乘法逆元呢? 当我们要求(a/b) mod p的值,且a很大,无法直接求得a/b的值时,我们就要用到乘法逆元. 我们可以通过求b关于p的乘法逆元k,将a乘上k再模p,即(a*k) mod p.其结果与(a/b) mod p等价. 证: 根据b*k≡1 (mod p)有b*k=p*x+1. k=(p*x+1)/b. 把k代入(a*k) mod p,得:(a*(p*x+1)/b) mod p               

乘法逆元及其应用

满足 a * k ≡ 1 (mod p) 的k 叫做 a关于p的乘法逆元.另一种表达方法是 k ≡ a-1 (mod p) 逆元在密码学中有广泛应用,AES密码体系的字节替代就是运用了逆元.(不知道说的smg) 应用: 我们知道(a+b)%p=(a%p+b%p)%p (a*b)%p=(a%p)*(b%p)%p 而求(a/b)%p时,可能会因为a是一个很大的数,不能直接算出来,却又不能(a/b)%p=(a%p/b%p)%p. 但是可以通过 k ≡ b-1 (mod p)  a / b = a *

loj #110. 乘法逆元

#110. 乘法逆元 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 这是一道模板题. 给定正整数 n nn 与 p pp,求 1∼n 1 \sim n1∼n 中的所有数在模 p pp 意义下的乘法逆元. 输入格式 一行两个正整数 n nn 与 p pp 输出格式 n nn 行,第 i ii 行一个正整数,表示 i ii 在模 p pp 意义下的乘法逆元. 样例 样例输入 10 13 样例输出

乘法逆元

定义:当(a,p)=1时,存在ax≡1(mod p),则x叫作a在模p意义下的乘法逆元. 求法: 1.当p为质数时,由费马小定理,得ap-1≡1(mod p),即(a·ap-2)≡1(mod p),则a在模p意义下的乘法逆元是ap-2,直接用快速幂可求得. 2.当p不为质数时,用扩展欧几里得算法求a的逆元. 代码: 1 int exgcd(int a,int b,int &x, int &y) 2 { 3 int d=a; 4 if(b!=0){ 5 d=exgcd(b,a%b,y,x);