目录
欧拉函数
质因数分解实现(适用于单次求值)
含义:小于n且与n互素的正整数个数。
积性:如果gcd(n, m) = 1,则
求值公式:
特别地:
注意:此实现需要提前调用euler()筛素数函数
代码:
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
// 欧拉线性筛素数
const int LIM = 1e6 + 10; /* The limit of number to be test */
int prime[LIM / 3];
bool notPrime[LIM];
int primepos;
void euler() {
int tmp = 0;
memset(notPrime, false, sizeof notPrime);
primepos = 0;
for (int i = 2; i < LIM; i++) {
if (!notPrime[i]) prime[primepos++] = i;
for (int j = 0; j < primepos && (tmp = i * prime[j]) < LIM; j++) {
notPrime[tmp] = true;
if (!(i % prime[j])) break;
}
}
}
LL fastPow(LL base, LL p) {
LL ret = 1;
do {
if (p & 1) ret = ret * base;
base = base * base;
} while (p >>= 1);
return ret;
}
// 欧拉函数
int phi(int n) {
int ret = 1;
for (int i = 0; i < primepos && n; i++) {
int cnt = 0;
while (!(n % prime[i])) n /= prime[i], cnt++;
if (cnt) ret *= fastPow(prime[i], cnt - 1) * (prime[i] - 1);
}
return ret;
}
int main(void) {
euler();
for (int i = 1; i <= 10; i++) {
printf("phi(%d) = %d\n", i, phi(i));
}
return 0;
}
下面是更简便且时间复杂度为O(sqrt(n))的方法:
LL phi(LL n) {
LL ret = 1;
for (LL i = 2; i * i <= n; i++) {
if (!(n % i)) {
ret *= i - 1;
n /= i;
while (!(n % i))
ret *= i, n /= i;
}
}
if (n ^ 1) ret *= (n - 1);
return ret;
}
Time Complexity: O(Sqrt(n))
打表实现
递推性质:
我们假设对于每个数n,它的最小质因子是p,接下来的递推分两类讨论,
如果
则
如果
则
简单证明:
对于一个数m,可以将其某个质因子拆出来,如下:
则如果p | m,即k不等于0,那么有:
如果k等于0,则m中不含有因子p,显然gcd(m, p) = 1,则可直接使用欧拉函数的积性公式:
令n = m * p即得到上述递推式。
证毕。
如果取n的最小质因子p,则可以通过筛素数打表来找到每个数n的p,这一点十分便于实现,于是我们取p为n的最小质因子。
代码:
#include <iostream>
using namespace std;
inline int mini(int a, int b) {return a < b ? a : b;}
// 使用欧拉线性筛打最小质因子表
const int LIM = 1e6 + 10; /* The limit of number to be test */
int prime[LIM / 3];
int miniFactor[LIM];
int primepos;
void euler() {
int tmp;
for (int i = 2; i < LIM; i++) {
if (!miniFactor[i]) prime[primepos++] = i, miniFactor[i] = i;
for (int j = 0; j < primepos && (tmp = i * prime[j]) < LIM; j++) {
miniFactor[tmp] = mini(prime[j], miniFactor[i]);
if (!(i % prime[j])) break;
}
}
}
int phi[LIM];
void phiTable() {
phi[1] = 1;
for (int i = 2; i < LIM; i++) {
phi[i] = phi[i / miniFactor[i]]
* ((i / miniFactor[i]) % miniFactor[i] ? miniFactor[i] - 1 : miniFactor[i]);
}
}
int main(void) {
// mini factor test
euler();
for (int i = 1; i <= 10; i++) {
printf("the mini factor of %d is %d\n", i, miniFactor[i]);
}
printf("the mini factor of 47 is %d\n", miniFactor[47]);
printf("the mini factor of 61 is %d\n", miniFactor[61]);
printf("the mini factor of 561 is %d\n", miniFactor[561]);
// phi table test
phiTable();
for (int i = 1; i <= 10; i++) {
printf("phi(%d) = %d\n", i, phi[i]);
}
for (int i = 1; i < LIM; i++)
if (phi[i] <= 0) printf("Overflow at %d\n", i);
return 0;
}
Time Complexity: O(n)
上面的方法实际上复杂度为2n,因为预先打出了一个最大素因子表,但是实际上可以直接将欧拉函数打表与素数筛选操作合并,如下。
直接打表:
#include <iostream>
#include <algorithm>
using namespace std;
const int LIM = 1e6 + 10;
int pricur = 0;
int prime[LIM / 3];
int phi[LIM];
void build_phi() {
int tmp;
phi[1] = 1;
for (int i = 2; i < LIM; i++) {
if (!phi[i]) {
phi[i] = i - 1;
prime[pricur++] = i;
}
for (int j = 0; (tmp = i * prime[j]) < LIM; j++) {
if (i % prime[j]) {
phi[tmp] = phi[i] * (prime[j] - 1);
} else {
phi[tmp] = phi[i] * prime[j];
break;
}
}
}
}
int main(void) {
build_phi();
for (int i = 1; i < 100; i++) {
printf("[%d] : %d\n", i, phi[i]);
}
return 0;
}
Time Comlexity: O(n)
欧拉函数的性质
偶数性质
除了1和2的欧拉函数值为1外,其余正整数的欧拉函数值均为偶数,因为对于数a如果满足a < n且gcd(a, n) = 1,即a与n互质,那么一定有gcd(n – a, n) = 1成立,那么对于小于n的与n互质的正整数,总是可以用n减去它找到另外一个与n互质的正整数,如果担心n – a = a是否会导致上面的规律被破坏,可以特别考虑一下,因为唯一一个满足n – x = x的数x是当n为偶数时的n / 2,但是显然这个数不与n互质,有gcd(n / 2, n) = n / 2。因此上面的规律可以确定存在。