藍橋杯真題:純質數

小葉Little_Ye發表於2022-03-12

完整格式連結:https://blog.imakiseki.cf/2022/03/11/acmoi/lanqiao/chun-zhi-shu/

藍橋杯 2021 年國賽真題《純質數》的 Python 解法。

藍橋杯 2021 年國賽真題:純質數。

題目連結:https://www.lanqiao.cn/problems/1561/learning/(需要登入)。

題目大意

輸出 1 到 20210605 之間(包括兩端)的“純質數”(指十進位制各數位皆為質數的質數,1 不視作質數)。

分析

Python

本題是填空題,原則上無需時間複雜度較低的程式也可被接受。當然,這裡將給出非填空題的解法。

Python 在大資料規模的迴圈上耗時較大,而本題顯然繞不開判斷質數這一話題——乍看本題給出的資料規模達到了千萬級,判斷其是否為質數最壞情況下需要千級別的迴圈,而我們要判斷 20210605 個數字是否為質數,這個耗時顯然是不可接受的(預測在 C/C++ 下也難以接受)。

因此我們可以考慮能否降低需要判斷是否為質數的數字的數量。注意到本題中所提到的“純質數”不僅要求其本身是質數,還要求十進位制各數位也為質數——這下我們可以先借此排除掉一定不是純質數的數字,繼而大大簡化運算量。

經過分析,我們得知如果一個數要滿足“十進位制各數位為質數”這一條件,必須滿足各個數位只可取 2、3、5、7 中的一個。粗略來看,我們省下了約五分之三的運算量。而更進一步,對於不小於 10 的數字,個位數若為 2 或 5,則一定是合數,故也可提前去除。

為了更好地實現上面的需求,我們最好是自行生成純質數的“候選”,而不是生成一個長度為 20210605 的列表再刪除不符合條件的。每一個數位有可以選擇的數字,而這些選擇都是互不干擾的,因此我們可以利用標準庫 itertools 裡的 product() 生成器生成笛卡爾積,以便於快速生成符合條件的數字。實現這一需求的主要程式碼如下:

from itertools import product

# (不小於 10 的數字)非個位可取的數字集合、個位可取的數字集合
_a, _b = (2, 3, 5, 7), (3, 7)

# 生成“候選”數字的生成器
def candidate_gen():
    # 1 位數
    for i in _a:
        yield i
    # 2~8 位數
    for i in range(2, 9):
        # star expression,表示生成 (i-1) 個 `_a`,一個 `_b`
        for j in product(*([_a] * (i-1)), _b):
            # `j` 是一個由各個數位組成的元組,需要先將其拼成一個整數
            x = packtup(j)
            # 超出範圍,終止生成
            if x > 20210605:
                return
            yield x

判斷質數的程式碼較為簡單,在此不作詳述,注意設定迴圈時除數的上限略高於目標數字的算術平方根即可。

完整程式碼

Python

from itertools import product

_a, _b = (2, 3, 5, 7), (3, 7)

def packtup(t):
  return sum(map(lambda i: t[::-1][i] * 10 ** i, range(len(t))))

def candidate_gen():
  for i in _a:
    yield i
  for i in range(2, 9):
    for j in product(*([_a] * (i-1)), _b):
      x = packtup(j)
      if x > 20210605:
        return
      yield x

def isprime(x):
  if x == 1:
    return False
  if x == 2:
    return True
  if x % 2 == 0:
    return False
  for i in range(3, int(x ** 0.5) + 1):
    if x % i == 0:
      return False
  return True

print(len(list(filter(isprime, candidate_gen()))))

相關文章