Project Euler Problem 74 (C++和Python代码实现和解析)

本文深入探讨了欧拉项目第74题中的数字阶乘链问题,通过分析找到起始数与链长之间的规律,利用C++和Python代码实现了解决方案,揭示了起始数与特定链长之间的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Problem 74 : Digit factorial chains

The number 145 is well known for the property that the sum of the factorial of its digits is equal to 145:

1! + 4! + 5! = 1 + 24 + 120 = 145

Perhaps less well known is 169, in that it produces the longest chain of numbers that link back to 169; it turns out that there are only three such loops that exist:

169 → 363601 → 1454 → 169
871 → 45361 → 871
872 → 45362 → 872

It is not difficult to prove that EVERY starting number will eventually get stuck in a loop. For example,

69 → 363600 → 1454 → 169 → 363601 (→ 1454)
78 → 45360 → 871 → 45361 (→ 871)
540 → 145 (→ 145)

Starting with 69 produces a chain of five non-repeating terms, but the longest non-repeating chain with a starting number below one million is sixty terms.

How many chains, with a starting number below one million, contain exactly sixty non-repeating terms?

1. 欧拉项目第74道题 数字阶乘链(序列)

数145有众所周知的特性:它的各位上数字的阶乘之和等于145:
1! + 4! + 5! = 1 + 24 + 120 = 145

也许不太为人所知的是169,因为它产生了连接回到169的最长的数字链;原来只有三个这样的循环存在:
169 → 363601 → 1454 → 169
871 → 45361 → 871
872 → 45362 → 872

起始数是69,产生一个有五个不重复项的链(序列),但是起始数小于一百万,产生的最长的不重复的链(序列)有60项。

69 → 363600 → 1454 → 169 → 363601 (→ 1454)
78 → 45360 → 871 → 45361 (→ 871)
540 → 145 (→ 145)

起始数低于一百万,正好包含60个不重复的项,有多少个链(序列)呢?

2. 求解分析

这道题常规方法并不难,但比较耗时间。常规方法就是从69一直到一百万逐一计算每个数开始的链(序列) 是否有60个项,把有60个项的链(序列)的个数累加,就是求解答案。

在计算每一个链(序列)的时候,把各个项放进集合里,如果有重复的项出现,链(序列)产生的过程结束,再判断集合里是否有60个不重复的项。

因为每个项都是一个数的阶乘的和,所以,我们把数字0到9的阶乘预先算出来,保存在数组中,可以避免重复计算0到9的阶乘。

常规方法肯定可以计算出正确的解。

后来,我做了一些分析,所有的有60项不重复的链(序列)有什么特点呢?主要是第一项有何特点?结果发现:在小于106的起始数,只要数的各位阶乘之和是367945或者367954,也就是说第一项是367945或者367954,那么就能产生60个不重复的链(序列)。

当起始数小于107或108时,也有同样的规律存在,但是第一项的集合不同,需要计算出来。

起始数小于106: 链(序列)的第一项的集合为 {367945,367954}
起始数小于 107: 链(序列)的第一项的集合为 {1479 1497 367945 367954 373944 735964}
起始数小于108: 链(序列)的第一项的集合为 {1479 1497 367945 367954 373944 379443 379465 732249 732294 735964 1129734}

3. C++ 代码实现

按照求解分析的描述,C++用代码实现了普通常规方法,也用C++11代码实现了特殊规律的方法。

C++ 代码 (打开编译开关 CPP_11 时使用特殊规律的方法, 否则,使用常规方法)

#include <iostream>
#include <set>
#include <vector>
#include <cmath>
#include <cassert>
#include <ctime>
    
#define UNIT_TEST  // print first term in chains
#define CPP_11     // take first terms' signatures method to find chains    

using namespace std;

class PE0074
{
private:
    static const int m_numOfTerms = 60; // sixty non-repeating terms
    int m_FactorialsArray[10];  // 0..9

#ifdef CPP_11
    set<int> firstTermsSignatures = { 367945, 367954 }; // first terms below 10^6
#else
    set<int> firstTermsSignatures;    // first terms' set
#endif
 
	bool checkFirstTermInChain(int number);

public:
    int getSumOfDigitFactorial(int n);
    int getNumOfNonRepeatingTermsInChain(int number);
    int getNumOfSixtyNonRepeatingTermsChains(int max_n);

    void printFirstTermsSet();

    PE0074();
};

PE0074::PE0074()
{
    m_FactorialsArray[0] = 1;

    for (int i = 1; i < 10; i++)
    {
        m_FactorialsArray[i] = i * m_FactorialsArray[i - 1];
    }
}

int PE0074::getSumOfDigitFactorial(int n)
{
    int sum = 0;

    while (n > 0)
    {
        sum += m_FactorialsArray[n % 10];
        n /= 10;
    }

    return sum;
}

int PE0074::getNumOfNonRepeatingTermsInChain(int number)
{
    set<int> dfChain;

    dfChain.insert(number);

    int numOfTerms, sof = number;;

    for (numOfTerms = 1; numOfTerms < m_numOfTerms; numOfTerms++)
    {
        sof = getSumOfDigitFactorial(sof);

        dfChain.insert(sof);

        if (numOfTerms == dfChain.size())
        {
            break; // sof is a repeating term
        }
    }

    if (m_numOfTerms == numOfTerms)
    {
        firstTermsSignatures.insert(getSumOfDigitFactorial(number));
    }
    return numOfTerms;
}

bool PE0074::checkFirstTermInChain(int number)
{
    int sof = getSumOfDigitFactorial(number);

    for (auto firstTerm : firstTermsSignatures)
    {
        if (sof == firstTerm)
        {
            return true;
        }
    }
    return false;
}

int PE0074::getNumOfSixtyNonRepeatingTermsChains(int max_n)
{
    int numOfChains = 0;
    for (int number = 69; number <= max_n; number++)
    {
        // find chains containing exactly sixty non-repeating digit factorial terms 
#ifdef CPP_11
        if (true == checkFirstTermInChain(number))
#else
        if (m_numOfTerms == getNumOfNonRepeatingTermsInChain(number))
#endif
        {
            numOfChains++;  
        }
    }
    return numOfChains;
}
    
void PE0074::printFirstTermsSet()
{
    set<int>::iterator iter;

    cout << "The first terms in the chains : ";
    for (iter = firstTermsSignatures.begin(); iter != firstTermsSignatures.end(); iter++)
    {
        cout << *iter << " ";
    }
    cout << endl;
}


int main()
{
    clock_t start = clock();

    PE0074 pe0074;

    assert(145 == pe0074.getSumOfDigitFactorial(145));
    assert(3 == pe0074.getNumOfNonRepeatingTermsInChain(169));

    int max_d = (int)pow(10, 6.0);
    int numOfChains = pe0074.getNumOfSixtyNonRepeatingTermsChains(max_d);

    cout << numOfChains << " chains, with a starting number below " << max_d << " contain" << endl;
    cout << "exactly sixty non-repeating digit factorial terms chains." << endl;

#ifdef UNIT_TEST
    pe0074.printFirstTermsSet();
#endif

    clock_t finish = clock();
    double duration = (double)(finish - start) / CLOCKS_PER_SEC;
    cout << "C++ application running time: " << duration << " seconds" << endl;

    return 0;
}

4. Python 代码实现

按照求解分析的描述,Python用代码实现了普通常规方法,也用Python代码实现了特殊规律的方法。两种方法的运行时间相差太多了,两种方法都需要知道,各有特点。

Python 代码

import time

class PE0074(object):
    def __init__(self):
        self.m_FactorialsArray = [1] * 10
        for i in range(2, 10):
            self.m_FactorialsArray[i] = i*self.m_FactorialsArray[i-1]
        self.firstTerms_set = set()

    def getSumOfDigitFactorial(self, n):
        if 0 == n: return 1
        total = 0
        while n > 0:
            total, n = total + self.m_FactorialsArray[n % 10], n//10
        return total
    
    def getNumOfNonRepeatingTermsInChain(self, number):
        dfChains, sof = set(), number
        dfChains.add(sof)

        for i in range(1 , 60):
            sof = self.getSumOfDigitFactorial(sof);
            dfChains.add(sof)
            numOfTerms = len(dfChains)
            if i == numOfTerms:
                break   # num is a repeating term

        if numOfTerms == 60:
             self.firstTerms_set.add(self.getSumOfDigitFactorial(number))

        return numOfTerms
    
    def getNumOfSixtyNonRepeatingTermsChains(self, max_n):
        numOfChains = 0
        for number in range(69, max_n+1):
            if 60 == self.getNumOfNonRepeatingTermsInChain(number): 
                numOfChains += 1  # contain exactly sixty non-repeating digit factorial Chains

        return numOfChains

def main():
    start = time.process_time()
    
    pe0074 = PE0074()

    assert 145 == pe0074.getSumOfDigitFactorial(145)
    assert  3  == pe0074.getNumOfNonRepeatingTermsInChain(169)

    numOfChains = pe0074.getNumOfSixtyNonRepeatingTermsChains(10**6)

    print(numOfChains, "chains, with a starting number below one million")
    print("contain exactly sixty non-repeating digit factorial Chains.")

    print('The set of the first terms in the chains :', pe0074.firstTerms_set)

    end = time.process_time()

    print('CPU processing time :', end-start)

if  __name__ == '__main__':
    main()

Python代码 II

import time

class PE0074(object):
    def __init__(self):
        self.m_FactorialsArray = [1] * 10
        for i in range(2, 10):
            self.m_FactorialsArray[i] = i*self.m_FactorialsArray[i-1]

    def getSumOfDigitFactorial(self, n):
        total = 0
        while n > 0:
            total, n = total + self.m_FactorialsArray[n % 10], n // 10
        return total
    
    def getNumOfSixtyNonRepeatingTermsChains(self, max_n):
        firstNumberSignatures = [367945, 367954]
        numOfChains = len([n for n in range(69, max_n+1) \
            if self.getSumOfDigitFactorial(n) in firstNumberSignatures])
        return numOfChains

def main():
    start = time.process_time()
    
    pe0074 = PE0074()

    assert 145 == pe0074.getSumOfDigitFactorial(145)

    numOfChains = pe0074.getNumOfSixtyNonRepeatingTermsChains(10**6)

    print(numOfChains, "chains, with a starting number below one million")
    print("contain exactly sixty non-repeating digit factorial Chains.")

    end = time.process_time()

    print("CPU processing time :", end-start)

if  __name__ == '__main__':
    main()
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值