[关闭]
@Dmaxiya 2020-08-24T15:43:47.000000Z 字数 10985 阅读 1853

欧拉函数相关数论

博文


欧拉函数

欧拉函数 的百度百科定义是:“对正整数 ,欧拉函数是小于 的正整数中与 互质的数的数目 。”不如将小于改成小于等于,倒是省去 的特例。关于欧拉函数的证明,不多赘述(但仍强烈建议先看懂“证明”链接中的文章)。本文侧重于欧拉函数在编程竞赛中的应用,这里只将其中三个知识点提出来(其他公式皆可由这三个公式推导得出):
1.


2.

3.

素数打表

素数表较常与欧拉函数一同出现,除了一些特殊的数字外,若想要较快地求出某个数字的欧拉函数值,也需要先打出一张质数表来。打出小于 的所有质数表的基本思想如下:
1. 用 visit 数组记录值 是否为合数,若为合数,则 visit[i] = true,否则为 false。用 prime 数组储存素数的值,即所求素数表;
2. 将 遍历,如果当前值为素数(visit 值未被设置为 true),则记录该数字到素数表中;
3. 对于遍历的任何数字 ,都用当前素数表中所有的素数与 相乘,将乘积的 visit 值设为 true,表示该值为合数;
4. 优化:当 为当前素数表中某个素数的倍数时,跳出第三步的循环。这里需要说明一下,首先素数表中所有的数字为从小到大储存,且 值也是从小到大遍历,举个例子:当我们遍历到 时,若不 break,则会将 visit[i*prime]=visit[75] 设为 true,而往后遍历到 时,会再次把 visit[75] 设 为true,于是就出现了重复操作,这就是第四步所优化的地方。
第 4 步优化的简单证明:
,则对于 ,有:


可知:,由遍历次序可知, 将随着大循环的 i++ 在后面被遍历到,所以若出现 的情况,就不必再往后遍历 了,若觉得证明不太容易理解,可以先看代码再对照代码进行理解。

代码:

  1. const int maxn = 10000 + 100;
  2. int cnt;
  3. int prime[maxn];
  4. bool visit[maxn];
  5. // n 表示要打的素数表最大值,cnt 表示素数的总数
  6. void Prime(int n) {
  7. cnt = 0;
  8. memset(visit, 0, sizeof(visit));
  9. for(int i = 2; i < n; ++i) {
  10. if(!visit[i]) prime[cnt++] = i;
  11. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  12. visit[i * prime[j]] = true;
  13. if(i % prime[j] == 0) break;
  14. }
  15. }
  16. }

求欧拉函数

要求欧拉函数,根据公式,就需要求出 的所有素数因子,庆幸我们有这样一条性质:大于 的素数因子最多只有 个,因此我们只需要从 找到 ,边找边除,就能得到最后一个素数。
证明:如果存在两个大于 的素数因子,设分别为 ,则有 ,其中 ,故矛盾。
由于可以在 时间复杂度内求得 的欧拉函数,根据一般题目的内存限制,我们可以打的素数表大小大概在 左右,因此用这种方法,我们可以求得 大小的欧拉函数。

代码

  1. int euler(int n) {
  2. if(n == 1) return 1;
  3. int ret = n;
  4. for(int j = 0; j < cnt && prime[j] * prime[j] <= n; ++j) {
  5. if(n % prime[j] == 0) {
  6. ret = ret / prime[j] * (prime[j] - 1);
  7. while(n % prime[j] == 0) n /= prime[j];
  8. }
  9. }
  10. if(n > 1) ret = ret / n * (n - 1);
  11. return ret;
  12. }

欧拉函数打表

除了打完素数表后根据公式求任意值的欧拉函数,其实也可在给素数打表的同时求出数字 的欧拉函数值,相对于上面一种方法的限制,即不能求太大数字的欧拉函数,只能在内存限制范围内打表。
欧拉函数打表主要根据以下几点:
1. ;
2. 若 为素数,则 ,显然对于任何素数 ,所有小于它的正整数都与其互质。素数只有自己一个素数因子,代入上面的欧拉函数也可计算得 ;
3. 若 ,则


4. 若 ,则

由以上几点,就可以在打素数表的同时,打欧拉函数表了。

代码

  1. const int maxn = 10000 + 100;
  2. int cnt;
  3. int prime[maxn], phi[maxn];
  4. bool visit[maxn];
  5. // n 表示要打的素数表最大值,cnt 表示素数的总数
  6. void Prime(int n) {
  7. cnt = 0;
  8. phi[1] = 1;
  9. memset(visit, 0, sizeof(visit));
  10. for(int i = 2; i < n; ++i) {
  11. if(!visit[i]) prime[cnt++] = i, phi[i] = i - 1;
  12. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  13. int k = i * prime[j];
  14. visit[k] = true;
  15. if(i % prime[j] == 0) {phi[k] = phi[i] * prime[j]; break;}
  16. else phi[k] = phi[i] * (prime[j] - 1);
  17. }
  18. }
  19. }

欧拉函数习题

基础知识部分完毕,下面是一些相关习题。

The Euler function

题目链接

HDU 2824: The Euler function

题意

多组数据,输入 ,输出 的值,其中

题解

打表暴力再做前缀和,注意一下用 long long。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <vector>
  10. #include <list>
  11. #include <queue>
  12. #include <stack>
  13. #include <map>
  14. #include <set>
  15. #include <algorithm>
  16. using namespace std;
  17. #define LL long long
  18. #define For(i, a, b) for(int i = (a), _##i = (b); i <= _##i; ++i)
  19. #define Rof(i, a, b) for(int i = (a), _##i = (b); i >= _##i; --i)
  20. #define Mem(a, b) memset(a, b, sizeof(a))
  21. #define Cpy(a, b) memcpy(a, b, sizeof(b))
  22. const int maxn = 3000000 + 100;
  23. int cnt, a, b;
  24. LL ans;
  25. LL prime[maxn], phi[maxn];
  26. bool visit[maxn];
  27. void Prime(int n) {
  28. cnt = 0;
  29. phi[1] = 1;
  30. Mem(visit, 0);
  31. For(i, 2, n - 1) {
  32. if(!visit[i]) prime[cnt++] = i, phi[i] = i - 1;
  33. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  34. int k = i * prime[j];
  35. visit[k] = true;
  36. if(i % prime[j] == 0) {phi[k] = phi[i] * prime[j]; break;}
  37. else phi[k] = phi[i] * (prime[j] - 1);
  38. }
  39. }
  40. For(i, 1, n - 1) {
  41. phi[i] += phi[i - 1];
  42. }
  43. }
  44. int main() {
  45. #ifdef LOCAL
  46. freopen("test.txt", "r", stdin);
  47. #endif // LOCAL
  48. ios::sync_with_stdio(false);
  49. Prime(maxn - 50);
  50. while(scanf("%d%d", &a, &b) != EOF) {
  51. printf("%I64d\n", phi[b] - phi[a - 1]);
  52. }
  53. return 0;
  54. }

Happy 2006

题目链接

POJ 2773: Happy 2006

题意

多组数据,输入 ,输出第 小的与 互质的数,其中

题解

可得,所有与 互质 的数以 为周期变化,所以只需要求得这个周期 ,再暴力在周期内搜一遍即可,要注意取膜等于 0 的情况。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <vector>
  10. #include <list>
  11. #include <queue>
  12. #include <stack>
  13. #include <map>
  14. #include <set>
  15. #include <algorithm>
  16. using namespace std;
  17. #define LL long long
  18. #define For(i, a, b) for(int i = (a), _##i = (b); i <= _##i; ++i)
  19. #define Rof(i, a, b) for(int i = (a), _##i = (b); i >= _##i; --i)
  20. #define Mem(a, b) memset(a, b, sizeof(a))
  21. #define Cpy(a, b) memcpy(a, b, sizeof(b))
  22. const int maxn = 3000000 + 100;
  23. int cnt, m, k, ans, time;
  24. int prime[maxn], phi[maxn];
  25. bool visit[maxn];
  26. void Prime(int n) {
  27. cnt = 0;
  28. phi[1] = 1;
  29. Mem(visit, 0);
  30. For(i, 2, n - 1) {
  31. if(!visit[i]) prime[cnt++] = i, phi[i] = i - 1;
  32. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  33. int k = i * prime[j];
  34. visit[k] = true;
  35. if(i % prime[j] == 0) {phi[k] = phi[i] * prime[j]; break;}
  36. else phi[k] = phi[i] * (prime[j] - 1);
  37. }
  38. }
  39. }
  40. int gcd(int a, int b) {
  41. if(b == 0) return a;
  42. return gcd(b, a % b);
  43. }
  44. int main() {
  45. #ifdef LOCAL
  46. freopen("test.txt", "r", stdin);
  47. #endif // LOCAL
  48. ios::sync_with_stdio(false);
  49. Prime(maxn - 50);
  50. while(scanf("%d%d", &m, &k) != EOF) {
  51. time = k / phi[m];
  52. k %= phi[m];
  53. if(k == 0) k = phi[m], --time;
  54. For(i, 1, m) {
  55. if(gcd(i, m) == 1) {
  56. --k;
  57. }
  58. if(k == 0) {
  59. ans = i;
  60. break;
  61. }
  62. }
  63. printf("%d\n", ans + time * m);
  64. }
  65. return 0;
  66. }

Divisors

题目链接

POJ 2992: Divisors

题意

多组数据,输入 ,输出 的约数个数,其中 , 且计算结果在 long long 范围内。

题解

首先要求任意数字 的约数个数,要先将 分解质因数为 ,可以知道 的所有约数都由这些数字组合相乘得到,任意质因数 都可以从 中任取一个次数去乘,因此 的约数个数即
其次,,所以可以将从 所有数字的质因数字数表打出来,对所有质因数做前缀和(因为 的分子分母都由连续数字相乘),每次询问相减即可。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <vector>
  10. #include <list>
  11. #include <queue>
  12. #include <stack>
  13. #include <map>
  14. #include <set>
  15. #include <algorithm>
  16. using namespace std;
  17. #define LL long long
  18. #define For(i, a, b) for(int i = (a), _##i = (b); i <= _##i; ++i)
  19. #define Rof(i, a, b) for(int i = (a), _##i = (b); i >= _##i; --i)
  20. #define Mem(a, b) memset(a, b, sizeof(a))
  21. #define Cpy(a, b) memcpy(a, b, sizeof(b))
  22. const int maxn = 450 + 100;
  23. int cnt, n, k;
  24. int prime[maxn];
  25. LL sum[maxn][100], fac[maxn][100];
  26. bool visit[maxn];
  27. void Prime(int n) {
  28. cnt = 0;
  29. Mem(visit, 0);
  30. For(i, 2, n - 1) {
  31. if(!visit[i]) prime[cnt++] = i;
  32. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  33. visit[i * prime[j]] = true;
  34. if(i % prime[j] == 0) break;
  35. }
  36. }
  37. }
  38. void Cal_fac(int n) {
  39. For(i, 1, n) {
  40. int num = i;
  41. For(j, 0, cnt - 1) {
  42. while(num % prime[j] == 0) {
  43. ++fac[i][j];
  44. num /= prime[j];
  45. }
  46. }
  47. }
  48. For(i, 1, n) {
  49. For(j, 0, cnt - 1) {
  50. sum[i][j] = sum[i - 1][j] + fac[i][j];
  51. }
  52. }
  53. }
  54. int main() {
  55. #ifdef LOCAL
  56. freopen("test.txt", "r", stdin);
  57. #endif // LOCAL
  58. ios::sync_with_stdio(false);
  59. Prime(maxn - 50);
  60. Cal_fac(maxn - 50);
  61. while(scanf("%d%d", &n, &k) != EOF) {
  62. LL ans = 1;
  63. For(i, 0, cnt - 1) {
  64. ans *= (1 + sum[n][i] - sum[n - k][i] - sum[k][i]);
  65. }
  66. printf("%I64d\n", ans);
  67. }
  68. return 0;
  69. }

Soldier and Number Game

题目链接

CodeForces 546D: Soldier and Number Game

题意

组数据,对于每一组数据的 ,求 的素数因子个数。

题解

对于 组测试数据,肯定需要预处理,由于阶乘是连续数字相乘,所以可以预处理出从 中所有素数因子个数和,即 的素数因子个数,最后相减即可。
打表可得,在 范围内的素数大概有 个素数,如果对 内所有数字 依次打表,计算量大概在 ,显然预处理会超时,需要对预处理进行优化。
这里只需要用到一点:如果 能被素数 整除,则 之间所有数字都不能被 整除,这样便可通过前一个能够被 整除的数直接找到下一个能够被其整除的数,步长将随着 的增大而增大,而时间复杂度为 (最后一个素数若大于 可直接求出)。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <vector>
  10. #include <list>
  11. #include <queue>
  12. #include <stack>
  13. #include <map>
  14. #include <set>
  15. #include <algorithm>
  16. using namespace std;
  17. #define LL long long
  18. #define For(i, a, b) for(int i = (a), _##i = (b); i <= _##i; ++i)
  19. #define Rof(i, a, b) for(int i = (a), _##i = (b); i >= _##i; --i)
  20. #define Mem(a, b) memset(a, b, sizeof(a))
  21. #define Cpy(a, b) memcpy(a, b, sizeof(b))
  22. const int maxn = 5000000 + 100;
  23. const int sq = sqrt((double)maxn);
  24. int cnt;
  25. int prime[maxn], phi[maxn];
  26. LL num[maxn];
  27. int tmp[maxn];
  28. LL sum[maxn];
  29. bool visit[maxn];
  30. int read() {
  31. char ch;
  32. int ret = 0;
  33. do {
  34. ch = getchar();
  35. } while(ch < '0' || ch > '9');
  36. do {
  37. ret = ret * 10 + ch - '0';
  38. ch = getchar();
  39. } while(ch >= '0' && ch <= '9');
  40. return ret;
  41. }
  42. char str[20];
  43. void write(LL n) {
  44. if(n == 0) {putchar('0'); return ;}
  45. int scnt = 0;
  46. while(n != 0) {
  47. str[++scnt] = n % 10 + '0';
  48. n /= 10;
  49. }
  50. while(scnt != 0) putchar(str[scnt--]);
  51. }
  52. void Prime(int n) {
  53. cnt = 0;
  54. phi[1] = 1;
  55. Mem(visit, 0);
  56. For(i, 2, n - 1) {
  57. if(!visit[i]) prime[cnt++] = i, phi[i] = i - 1;
  58. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  59. int k = i * prime[j];
  60. visit[k] = true;
  61. if(i % prime[j] == 0) {phi[k] = phi[i] * prime[j]; break;}
  62. else phi[k] = phi[i] * (prime[j] - 1);
  63. }
  64. }
  65. }
  66. int main() {
  67. #ifdef LOCAL
  68. freopen("test.txt", "r", stdin);
  69. #endif // LOCAL
  70. ios::sync_with_stdio(false);
  71. Prime(sq);
  72. For(i, 1, maxn) tmp[i] = i;
  73. For(i, 0, cnt - 1) {
  74. for(int j = prime[i]; j <= maxn; j += prime[i]) {
  75. while(tmp[j] % prime[i] == 0) {
  76. ++num[j];
  77. tmp[j] /= prime[i];
  78. }
  79. }
  80. }
  81. For(j, 2, maxn - 1) {
  82. if(tmp[j] != 1) ++num[j];
  83. sum[j] = sum[j - 1] + num[j];
  84. }
  85. int t, a, b;
  86. scanf("%d", &t);
  87. while(t--) {
  88. a = read(); b = read();
  89. write(sum[a] - sum[b]);
  90. putchar('\n');
  91. }
  92. return 0;
  93. }

Longge's problem

题目链接

POJ 2480: Longge's problem

题意

多组输入,对于每一个输入数字 ,求出

题解

首先:


由于 互质,所以 与任意正整数的最大公约数必然没有交集,上式成立,故 是积性函数,由积性函数性质可得:积性函数的和也是积性的,故 也是积性函数。所以,若将 分解为



其次,若 ,则 ,所以与 的公约数值为 的数字个数等于与 互质的数字个数相等,可以得到:

代入上式继续推导可得:

由于素数的 次幂的所有约数分别为该素数的 次幂,所以

若推导到这里便开始写程序,容易超时,还需要一步计算:若 ,则 ,所以:

注意这里 ,而 ,最后当 时,,而 的值为特判,不能由欧拉公式直接计算得到,故应单独分离出来进行计算。

过题代码

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstdlib>
  4. #include <cmath>
  5. #include <climits>
  6. #include <cfloat>
  7. #include <cstring>
  8. #include <string>
  9. #include <vector>
  10. #include <list>
  11. #include <queue>
  12. #include <stack>
  13. #include <map>
  14. #include <set>
  15. #include <algorithm>
  16. using namespace std;
  17. #define LL long long
  18. #define For(i, a, b) for(int i = (a), _##i = (b); i <= _##i; ++i)
  19. #define Rof(i, a, b) for(int i = (a), _##i = (b); i >= _##i; --i)
  20. #define Mem(a, b) memset(a, b, sizeof(a))
  21. #define Cpy(a, b) memcpy(a, b, sizeof(b))
  22. const int maxn = 100000 + 100;
  23. int cnt, fac;
  24. LL ans, n;
  25. int prime[maxn];
  26. bool visit[maxn];
  27. void Prime(int n) {
  28. cnt = 0;
  29. Mem(visit, 0);
  30. For(i, 2, n - 1) {
  31. if(!visit[i]) prime[cnt++] = i;
  32. for(int j = 0; j < cnt && i * prime[j] < n; ++j) {
  33. visit[i * prime[j]] = true;
  34. if(i % prime[j] == 0) break;
  35. }
  36. }
  37. }
  38. int main() {
  39. #ifdef LOCAL
  40. freopen("test.txt", "r", stdin);
  41. #endif // LOCAL
  42. ios::sync_with_stdio(false);
  43. Prime(maxn - 50);
  44. while(scanf("%I64d", &n) != EOF) {
  45. ans = 1;
  46. int sq = sqrt((double)n);
  47. for(int i = 0; prime[i] <= sq; ++i) {
  48. fac = 0;
  49. while(n % prime[i] == 0) {
  50. ++fac;
  51. n /= prime[i];
  52. }
  53. ans *= fac * (pow(prime[i], fac) - pow(prime[i], fac - 1)) + pow(prime[i], fac);
  54. }
  55. if(n != 1) ans *= 2 * n - 1;
  56. printf("%I64d\n", ans);
  57. }
  58. return 0;
  59. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注