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

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

数论


最近开始系统的学习 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. }

待更新。

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