Problem 72 : Counting fractions
Consider the fraction, n/d, where n and d are positive integers. If n<d and HCF(n,d)=1, it is called a reduced proper fraction.
If we list the set of reduced proper fractions for d ≤ 8 in ascending order of size, we get:
1/8, 1/7, 1/6, 1/5, 1/4, 2/7, 1/3, 3/8, 2/5, 3/7, 1/2, 4/7, 3/5, 5/8, 2/3, 5/7, 3/4, 4/5, 5/6, 6/7, 7/8
It can be seen that there are 21 elements in this set.
How many elements would be contained in the set of reduced proper fractions for d ≤ 1,000,000?
1. 欧拉项目第72道题 : 计数分数
考虑分数n/d,分子n 和 分母d 是正整数。 如果 n<d且n和d互质(n和d的最大公约数是1),分数n/d被叫做最简真分数。
如果我们按大小的升序列出d≤8的最简真分数集合,则得到
1/8, 1/7, 1/6, 1/5, 1/4, 2/7, 1/3, 3/8, 2/5, 3/7, 1/2, 4/7, 3/5, 5/8, 2/3, 5/7, 3/4, 4/5, 5/6, 6/7, 7/8
可以看出,这个集合包括21项(元素)。
当d≤1,000,000时,最简真分数的集合包括多少项呢?
2. 求解分析
第一种方法:
欧拉函数φ(n) (有时称为Phi函数), 用于确定小于n且与n互质的数的个数。例如:小于9 且与9互质的数有1, 2, 4, 5, 7, 和 8, 所以 φ(9)=6。
数n | 小于n且与n互质的数 | φ(n) | n/φ(n) |
---|---|---|---|
2 | 1 | 1 | 2 |
3 | 1,2 | 2 | 1.5 |
4 | 1,3 | 2 | 2 |
5 | 1,2,3,4 | 4 | 1.25 |
6 | 1,5 | 2 | 3 |
7 | 1,2,3,4,5,6 | 6 | 1.1666… |
8 | 1 ,3,5,7 | 4 | 2 |
9 | 1,2,4,5,7,8 | 6 | 1.5 |
10 | 1,3,7,9 | 4 | 2.5 |
如何求解φ(x) 呢?
通式:
其中p1, p2……pn为x的所有质因数,x是不为0的整数。
φ(1)=1(和1互质的数(小于等于1)就是1本身)。
我们需要找出x的所有质因数,然后计算出φ(x),最后d从2到max_d逐个把φ(d)累加求和,结果就是最简真分数的集合的包含的项数。
第二种方法:
其实我们可以借鉴质数筛子的方法,用类似的方法来实现。
3. C++ 代码实现
函数 calculatePhi(int x) 用来计算x的phi。我们先找到x的所有的质因数,然后用欧拉函数公式计算出phi值。因为每个数的质因数的个数不多,但为了避免频繁申请和释放内存,所以我们把原先使用的vector/set改成使用数组,这样做的效果确实很明显。
函数countAllReducedProperFractions(int max_d = m_max_n) 用来把d从2到max_d的phi(d)累加求和。
C++代码
#include <iostream>
#include <cmath>
#include <cassert>
#include <ctime>
using namespace std;
class PE0072
{
private:
static const int m_max_n = 1000000; // one million
static const int m_max_primeFactors = 15;
int m_primesFactors_array[m_max_primeFactors];
int m_numOfFactors;
int calculatePhi(int x);
public:
long long countAllReducedProperFractions(int max_d);
};
int PE0072::calculatePhi(int x)
{
double phi = x;
m_numOfFactors = 0;
for (int i = 2; i*i <= x; i++)
{
if (x % i == 0)
{
m_primesFactors_array[m_numOfFactors++] = i;
while (x % i == 0)
{
x /= i;
}
}
}
if (x != 1)
{
m_primesFactors_array[m_numOfFactors++] = x;
}
double p;
for (int i = 0; i < m_numOfFactors; i++)
{
p = m_primesFactors_array[i];
phi *= (p - 1)/p;
}
return (int)phi;
}
long long PE0072::countAllReducedProperFractions(int max_d = m_max_n)
{
long long sum = 0; // φ(1) = 1, so d=1 has no reduced proper fraction
for (int d=2; d<=max_d; d++)
{
sum += calculatePhi(d);
}
return sum;
}
int main()
{
clock_t start=clock();
PE0072 pe0072;
assert(21 == pe0072.countAllReducedProperFractions(8));
long long sum = pe0072.countAllReducedProperFractions();
cout << "For n <= 1,000,000, " << sum << " elements would ";
cout << "be contained in the set of reduced proper fractions" << endl;
clock_t finish = clock();
double duration=(double)(finish - start) / CLOCKS_PER_SEC;
cout << "C/C++ running time: " << duration << " seconds" << endl;
return 0;
}
C++代码 II (利用跟质数筛子类似的方法实现)
#include <iostream>
#include <vector>
#include <cassert>
#include <ctime>
using namespace std;
class PE0072
{
private:
static const int m_max_n = 1000000; // one million
public:
long long countReducedProperFractions(int max_d);
};
long long PE0072::countReducedProperFractions(int max_d = m_max_n)
{
vector<int> phi_vec(max_d+1);
for(int d=0; d<=max_d; d++)
{
phi_vec[d] = d;
}
for(int d=2; d<=max_d; d++)
{
if (d == phi_vec[d])
{
for (int k=d; k<=max_d; k+=d)
{
phi_vec[k] -= phi_vec[k] / d;
}
}
}
long long sum = 0; // φ(1) = 1, so d=1 has no reduced proper fraction
for (int d=2; d<=max_d; d++)
{
sum += phi_vec[d];
}
return sum;
}
int main()
{
clock_t start = clock();
PE0072 pe0072;
assert(21 == pe0072.countReducedProperFractions(8));
long long sum = pe0072.countReducedProperFractions();
cout << "For n <= 1,000,000, " << sum << " elements would ";
cout << "be contained in the set of reduced proper fractions" << endl;
clock_t finish = clock();
double duration=(double)(finish - start) / CLOCKS_PER_SEC;
cout << "C/C++ running time: " << duration << " seconds" << endl;
return 0;
}
4. Python 代码实现
Python采用跟C++一样的方法来实现。方法二的运行时间比方法一的运行时间明显少。
Python代码
class PE0072(object):
def __init__(self):
self.m_primeFactors_list = [ 0 ]*15 # max 15 prime factors
def calculatePhi(self, x):
""" calculate phi = x*phi*(1-1/p) for p in m_primeFactors_list"""
phi, numOfFactors = x, 0
for i in range(2, int(x**0.5)+1):
if 0 == x % i:
self.m_primeFactors_list[numOfFactors] = i
numOfFactors += 1
while 0 == x % i:
x //= i
if x != 1:
self.m_primeFactors_list[numOfFactors] = x
numOfFactors += 1
for p in self.m_primeFactors_list[:numOfFactors]:
phi *= (p-1)/p
return phi
def countReducedProperFractions(self, max_d):
total = 0 # d=1, φ(1)=1, has no reduced proper fraction
for d in range(2, max_d+1):
total += self.calculatePhi(d)
return total
def main():
pe0072 = PE0072()
assert 21 == pe0072.countReducedProperFractions(8)
total = pe0072.countReducedProperFractions(10**6)
print("For n <= 1,000,000, %d elements would" % total, end=' ')
print("be contained in the set of reduced proper fractions.")
if __name__ == '__main__':
main()
Python代码 II (利用跟质数筛子类似的方法实现)
import time
def countReducedProperFractions(max_d):
phi_list = [ n for n in range(max_d+1) ]
for n in range(2, max_d+1):
if phi_list[n] == n:
for k in range(n, max_d+1, n):
phi_list[k] -= phi_list[k] // n
return sum(phi_list[2:]) # d >= 2
def main():
start = time.process_time()
assert 21 == countReducedProperFractions(8)
total = countReducedProperFractions(10**6)
print("For n <= 1,000,000, %d elements would" % total, end=' ')
print("be contained in the set of reduced proper fractions.")
end = time.process_time()
print('PE0072 spent CPU processing time :', end-start)
if __name__ == '__main__':
main()