[关闭]
@ZCDHJ 2018-09-24T06:09:02.000000Z 字数 5205 阅读 824

一些比较基础的数论知识的学习笔记

数论


最近开始系统的学习 OI 中必不可少的数论知识(对我之前只会 exgcd

感觉很有必要做个笔记

这里面放的都是些比较基础的知识和证明

高深的东西我可能会另外开坑

就酱

长期不定期更新 弃坑


线性筛

线性筛算法中的每个合数都只会被其最小的质因子(也可以说是最大的因子)筛去,所以复杂度是 的。

过程大概就是枚举那每一个最大的因子与最小的质因子,需要注意的是如果那个最小的质因子是另一个枚举的因子的质因数,那么后面的质因子就应不再枚举,这里令素数表为

如果 ,那么 的最小质因子就是 及后面的质数也一样,如果这时不退出的话一些数就会被重复筛去。

下面线性筛模板用来 AC Luogu3383

  1. #include <iostream>
  2. #include <cstdio>
  3. const int MAXN = 1e7 + 5;
  4. int N, M, Tot;
  5. int Prime[700000];
  6. bool A[MAXN];
  7. int main()
  8. {
  9. scanf("%d %d", &N, &M);
  10. A[0] = A[1] = 1;
  11. for(int i = 2; i <= N; ++i)
  12. {
  13. if(!A[i]) Prime[++Tot] = i;
  14. for(int j = 1; j <= Tot && i * Prime[j] <= N; ++j)
  15. {
  16. A[i * Prime[j]] = 1;
  17. if(i % Prime[j] == 0) break;
  18. }
  19. }
  20. while(M--)
  21. {
  22. int q = read();
  23. if(A[q]) puts("No");
  24. else puts("Yes");
  25. }
  26. return 0;
  27. }

POJ2689 Prime Distance

算术基本定理

任意一个大于 的整数 都能分解为几个质数之积,就像下面这个形式


因为这个分解是惟一的,所以这个定理又叫”唯一分解定理“。

为什么是唯一的呢?因为如果任意一个 减一,那就需要再补一个 的因子,然而 都是质数,所以分解唯一。

代码就是分解质因数。。。

之前说了线性筛能够保证用每一个数最小的质因子来筛去某一个数,其实就是可以 求出每个数最小的质因子,那么就可以用来加速质因数分解。前几天打 cf 的时候遇到了这样的一道题。。。

因为懒癌,我就不贴线性筛加速的代码了

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cmath>
  4. const int MAXN = 1000 + 5;
  5. int N, Tot;
  6. int A[MAXN], B[MAXN];
  7. int main()
  8. {
  9. scanf("%d", &N);
  10. for(int i = 2; i <= std::sqrt(N); ++i)
  11. {
  12. if(N % i == 0)
  13. {
  14. A[++Tot] = i;
  15. while(N % i == 0)
  16. {
  17. N /= i;
  18. ++B[Tot];
  19. }
  20. if(N == 1) break;
  21. }
  22. }
  23. if(N > 1)
  24. {
  25. A[++Tot] = N;
  26. B[Tot] = 1;
  27. }
  28. for(int i = 1; i <= Tot; ++i) printf("%d %d\n", A[i], B[i]);
  29. return 0;
  30. }

然后写一些算术基本定理的推论

的正约数个数为

的所有正约数之和为

这两个式子都比较显然,就不写证明了。

欧几里得算法

这个没啥好讲的,但我还是要证明一下。

。对于 任意公因数 , 那么 ,也就是 ,所以 的公因数集合与 的公因数集合相同。

  1. #include <cstdio>
  2. int gcd(int a, int b)
  3. {
  4. if(b == 0) return a;
  5. else return gcd(b, a % b);
  6. }
  7. int main()
  8. {
  9. int a, b;
  10. scanf("%d %d", &a, &b);
  11. printf("%d\n", gcd(a, b));
  12. return 0;
  13. }

这个算法是 的,下面讲下更相减损术。

更相减损术

这个算法来自《九章算数》,它原本是为约分而设计的,但适用于任何求最大公因数的问题。

  1. 可半者半之,不可半者,副置分母、子之数,以少减多,更相减损,求其等也。以等数约之。

请自行翻译

大概就是有两个式子

这里默认

因为对于 的任意公因数 ,所以上式得证。

还有 ,这个式子显然成立。

在高精度下求 gcd 可考虑用更相减损术代替欧几里得。

  1. #include <cstdio>
  2. #include <algorithm>
  3. int gcd(int a, int b)
  4. {
  5. if(a < b) std::swap(a, b);
  6. if(b == 0) return a;
  7. if(a & 1 == 0 && b & 1 == 0) return gcd(a >> 1, b >> 1) << 1;
  8. return gcd(b, a - b);
  9. }
  10. int main()
  11. {
  12. int a, b;
  13. scanf("%d %d", &a, &b);
  14. printf("%d\n", gcd(a, b));
  15. return 0;
  16. }

Stein 算法

其实更相减损术很像这个 Stein 算法

众所周知,欧几里得算法求 gcd 很♂快

但是在算大整数的时候,需要试商,就会变慢。Stein 算法很好的解决了这个问题。

如果 中有一个奇数,那我们就可以将另一个数中的质因子 全部约去,因为这个时候两数的 gcd 不可能是 的倍数。

剩下的就和更相减损术差不多。

  1. #include <cstdio>
  2. #include <algorithm>
  3. int stein(int a, int b)
  4. {
  5. if(a == 0) return b;
  6. if(b == 0) return a;
  7. if(a & 1 == 0 && b & 1 == 0) return stein(a >> 1, b >> 1) << 1;
  8. if(a & 1 == 0) return stein(a >> 1, b);
  9. if(b & 1 == 0) return stein(a, b >> 1);
  10. if(a > b) return stein(b, a - b);
  11. else return stein(a, b - a);
  12. }
  13. int main()
  14. {
  15. int a, b;
  16. scanf("%d %d", &a, &b);
  17. printf("%d\n", stein(a, b));
  18. return 0;
  19. }

欧拉函数

欧拉函数等于 。也就是与 互质的数的个数。

算数基本定理有 ,那么

大概证明一下
的质因子 会在与 互质的数集合中筛去 个数,质因子 同理。因为一个数的质因子不会大于 ,所以 一定小于 ,那么在 中, 的倍数就会被筛去两次,所以容斥一下,答案加上
化简可得

分解 的质因数时可以求出

  1. #include <cstdio>
  2. #include <cmath>
  3. int N;
  4. inline int Phi()
  5. {
  6. int res = N;
  7. for(int i = 2; i <= sqrt(N); ++i)
  8. {
  9. if(N % i == 0)
  10. {
  11. res = res / i * (i - 1);
  12. while(N % i == 0) N /= i;
  13. }
  14. }
  15. if(N > 1) res = res / N * (N - 1);
  16. return res;
  17. }
  18. int main()
  19. {
  20. scanf("%d", &N);
  21. printf("%d\n", Phi());
  22. return 0;
  23. }

51NOD1040 最大公约数之和

然后有个性质

那么就可以线性筛出 内的

  1. #include <iostream>
  2. #include <cstdio>
  3. const int MAXN = 1e5 + 5;
  4. int N, Tot;
  5. int Prime[MAXN], Phi[MAXN];
  6. bool A[MAXN];
  7. inline int read()
  8. {
  9. register int x = 0;
  10. register char ch = getchar();
  11. while(!isdigit(ch)) ch = getchar();
  12. while(isdigit(ch))
  13. {
  14. x = x * 10 + ch - '0';
  15. ch = getchar();
  16. }
  17. return x;
  18. }
  19. void EulerSieve()
  20. {
  21. Phi[1] = 1;
  22. for(int i = 2; i <= N; ++i)
  23. {
  24. if(!A[i])
  25. {
  26. Prime[++Tot] = i;
  27. Phi[i] = i - 1;
  28. }
  29. for(int j = 1; j <= Tot && i * Prime[j] <= N; ++j)
  30. {
  31. A[i * Prime[j]] = 1;
  32. if(i % Prime[j] == 0)
  33. {
  34. Phi[i * Prime[j]] = Phi[i] * Prime[j];
  35. break;
  36. }
  37. else Phi[i * Prime[j]] = Phi[i] * (Prime[j] - 1);
  38. }
  39. }
  40. }
  41. int main()
  42. {
  43. N = read();
  44. EulerSieve();
  45. for(int i = 1; i <= N; ++i) printf("Phi(%d)=%d\n", i, Phi[i]);
  46. return 0;
  47. }

欧拉定理

如果正整数 互质,就有

我不会证。

推论:

费马小定理

为素数,有

其实就是欧拉定理中的 为素数时两边乘个 的结果。

那么只要会证欧拉定理就会证这个了

所以我不会证

拓展欧几里得

不写了。。。

 一堆同y

乘法逆元

我们都知道加减乘法都是满足同余性的。。。但除法就不一定了

现在就是要求出一个正整数 ,使得

注意 只有当 互质的时候才会存在

现在记

将这个式子化简,得到 。当 为质数的时候,直接上费马小定理。当 互质的时候,也可以使用 exgcd 来解同余方程。

前面也都说了费马小定理就是欧拉定理的一个子集对不对。。。

所以当 不是质数但与 互质的时候,除了 exgcd,也可以用欧拉定理来求逆元。

就是逆元

还是懒癌,这里只有费马小定理的代码。。。

还有一种线性递推逆元的算法

快速幂(费马小定理)

  1. #include <iostream>
  2. #include <cstdio>
  3. typedef long long LL;
  4. #define int LL
  5. int N, P;
  6. inline int Fpow(int x, int y)
  7. {
  8. int r = 1, base = x;
  9. while(y)
  10. {
  11. if(y & 1) r = (r * base) % P;
  12. base = (base * base) % P;
  13. y >>= 1;
  14. }
  15. return r % P;
  16. }
  17. signed main()
  18. {
  19. scanf("%lld %lld", &N, &P);
  20. for(int i = 1; i <= N; ++i) printf("%lld\n", Fpow(i, P - 2));
  21. return 0;
  22. }

拓展欧几里得

  1. #include <iostream>
  2. #include <cstdio>
  3. typedef long long LL;
  4. #define int LL
  5. int N, P, X, Y;
  6. inline int read()
  7. {
  8. register int x = 0;
  9. register char ch = getchar();
  10. while(!isdigit(ch)) ch = getchar();
  11. while(isdigit(ch))
  12. {
  13. x = x * 10 + ch - '0';
  14. ch = getchar();
  15. }
  16. return x;
  17. }
  18. void exgcd(int a, int b, int &x, int &y)
  19. {
  20. if(b == 0)
  21. {
  22. x = 1;
  23. y = 0;
  24. return;
  25. }
  26. exgcd(b, a % b, x, y);
  27. int t = y;
  28. y = x - (a / b) * y;
  29. x = t;
  30. }
  31. signed main()
  32. {
  33. N = read();
  34. P = read();
  35. for(int i = 1; i <= N; ++i)
  36. {
  37. exgcd(i, P, X, Y);
  38. while(X < 0) X += P;
  39. printf("%lld\n", X);
  40. }
  41. return 0;
  42. }

待更新。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注