Problem 70 : Totient permutation
Euler’s Totient function, φ(n) [sometimes called the phi function], is used to determine the number of positive numbers less than or equal to n which are relatively prime to n. For example, as 1, 2, 4, 5, 7, and 8, are all less than nine and relatively prime to nine, φ(9)=6.
The number 1 is considered to be relatively prime to every positive number, so φ(1)=1.
Interestingly, φ(87109)=79180, and it can be seen that 87109 is a permutation of 79180.
Find the value of n, 1 < n < 107, for which φ(n) is a permutation of n and the ratio n/φ(n) produces a minimum.
1. 欧拉项目的第70道题:欧拉函数的排列
欧拉函数φ(n) (有时称为Phi函数), 用于确定小于n且与n互质的数的个数。例如:小于9 且与9互质的数有1, 2, 4, 5, 7, 和 8, 所以 φ(9)=6。
数1 被认为对于每一个正整数来说都是互质的数, 所以 φ(1)=1.
有趣地是,φ(87109)=79180, 可以看出数87109 是数79180的一个排列。
对于1 < n < 107, 找到n, 确保φ(n) 是 n 的一个排列,且n/φ(n) 的值最小。
2. 求解分析
这道题跟Project Euler Problem 69 相关,都是有关欧拉函数的题。不同的是这道题要找到n, 让φ(n) 是 n 的一个排列, 且n/φ(n)最小。为了避免盲目的枚举所有的质数,我们用纸和笔分析后知道,当一个数仅有两个质因数的时候 n/φ(n) 会比较小,我们就缩小范围从质数列表中来找到n两个质因数p1, p2, 这样n = p1*p2, φ(n) = (p1-1) * (p2-1),判断φ(n) 是 n 的一个排列后,计算出n/φ(n)的值, 最后找到n, 保证φ(n) 是 n 的一个排列,且n/φ(n)的值最小。
3. C++ 代码实现
我们首先通过质数筛子找到所有的小于一千万的质数,保存到一个数组里(没有使用STL vector是为了节约时间),然后在函数里getTotientPermutation()缩小了范围,搜索p1 = m_primesArray[i] 和p2= m_primesArray[j], n = p1*p2, φ(n) = (p1-1) * (p2-1),然后通过函数getDigitsString()来判断:φ(n)是不是n的一个排列,找到更小的n/φ(n),最后找到最小的n/φ(n)的n。
值得说明的是,函数getDigitsString()使用了C++11的函数to_string(),再使用sort()排序,比用vector 保存一个数的各位上的数字,再排序,要快速很多。
C++11 的代码
#include <iostream>
#include <string> // to_string(), C++11
#include <cmath> // sqrt()
#include <ctime> // clock()
#include <algorithm> // sort()
using namespace std;
// #define UNIT_TEST
class PE0070
{
private:
static const int m_max_n = 10000000; // ten million
bool *m_primeSieve; // prime Sieve
int *m_primesArray;
int m_numOfPrimes = 0;
void getAllPrimes();
string getDigitsString(int number);
public:
PE0070()
{
m_primeSieve = new bool[m_max_n + 1];
m_primesArray = new int[m_max_n / 10];
getAllPrimes();
}
~PE0070() { delete [] m_primeSieve; delete [] m_primesArray; }
int getTotientPermutation();
};
// use prime Sieve of Eratosthenes to get all primes
void PE0070::getAllPrimes()
{
memset(m_primeSieve, true, (m_max_n + 1) * sizeof(bool));
m_primeSieve[0] = m_primeSieve[1] = false;
for (int i = 2; i <= (int)sqrt((double)m_max_n); i++)
{
if (true == m_primeSieve[i])
{
for (int j = i * i; j < m_max_n; j += i)
{
m_primeSieve[j] = false;
}
}
}
for (int i = 2; i < m_max_n; i++)
{
if (true == m_primeSieve[i])
{
m_primesArray[m_numOfPrimes++] = i;
}
}
}
string PE0070::getDigitsString(int number)
{
string digits_string = to_string(number);
sort(digits_string.begin(), digits_string.end());
return digits_string;
}
int PE0070::getTotientPermutation()
{
#ifdef UNIT_TEST
clock_t start = clock();
#endif
double min_n_div_phi = 10.0;
double n_div_phi;
int n, n0 = 0;
int phi, phi0 = 0;
int root = (int)sqrt((double)m_max_n);
int k = 0;
while (m_primesArray[k] <= root)
{
k++;
}
for (int i=k+100; m_primesArray[i] > root / 2; i--)
{
for (int j = i-1; m_primesArray[j] > root / 2; j--)
{
n = m_primesArray[i] * m_primesArray[j];
if (n > m_max_n) continue;
// phi = p1 * p2 * (1-/p1) * (1-1/p2)
// phi = (p1-1) * (p2-1)
phi = (m_primesArray[i] - 1) * (m_primesArray[j] - 1);
// check whether φ(n) is a permutation of n
if (getDigitsString(phi) == getDigitsString(n))
{
n_div_phi = (n*1.0) / phi;
if (n_div_phi < min_n_div_phi)
{
min_n_div_phi = n_div_phi;
n0 = n;
phi0 = phi;
}
}
}
}
cout << "For 1 < n < 10^7, when n = " << n0 << ", φ(n) = ";
cout << phi0 << " is a " << endl;
cout << "permutation of n and the ratio n / φ(n) produces a minimum." << endl;
#ifdef UNIT_TEST
cout << "And the minimum ratio n / φ(n) is " << min_n_div_phi << "." << endl;
clock_t finish = clock();
double duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "C++ running time: " << duration << " seconds" << endl;
#endif
return 0;
}
int main()
{
PE0070 pe0070;
pe0070.getTotientPermutation();
return 0;
}
4. Python 代码实现
Python跟C++采用了一样的方法,用质数筛子得到质数列表,然后缩小范围搜索合适的p1和p2,最后判断找到n,确保φ(n)是n的一个排列,且n / φ(n)的值最小。
Python 代码
def createPrimeSieve(n):
""" create prime sieve and return primes list """
sieve = [True] * n
sieve[0] = sieve[1] = False
for i in range(2, int(n**0.5)+1):
if True == sieve[i]:
for j in range(i*i, n, i):
sieve[j] = False
return [ i for i in range(1, n) if True==sieve[i] ]
def getTotientPermutation():
max_n, min_n_div_phi = 10**7, 10.0
root, k = int(max_n**0.5), 0
primes_list = createPrimeSieve(max_n)
while primes_list[k] <= root:
k += 1
i = k + 100
while primes_list[i] > root // 2:
j = i - 1
while primes_list[j] > root // 2:
n = primes_list[i] * primes_list[j]
if n > max_n :
j -= 1
continue
# phi = p1 * p2 * (1-/p1) * (1-1/p2)
# phi = (p1-1) * (p2-1)
phi = (primes_list[i] - 1) * (primes_list[j] - 1)
# check whether φ(n) is a permutation of n
if sorted(str(phi)) == sorted(str(n)):
n_div_phi = n / phi
if n_div_phi < min_n_div_phi:
min_n_div_phi, n0, phi0 = n_div_phi, n, phi
j -= 1
i -= 1
print("For 1 < n < 10^7, when n = %d, φ(n) = %d is a" %(n0, phi0))
print("permutation of n and the ratio n / φ(n) produces a minimum.")
print("And the minimum ratio n / φ(n) is %f." % min_n_div_phi)
def main():
getTotientPermutation()
if __name__ == '__main__':
main()