Python 実装

from __future__ import annotations
import os


def divisors(n: int) -> int:
    """
    n の約数のうち 2 で割り切れるものの個数を返す。

    【全単射による帰着】
        A = { d : d|n, 2|d }  ←→  B = { k : k | (n//2) }
        写像 φ: d → d/2  は A→B の全単射。
        よって |A| = |B| = #divisors(n // 2)

    Time Complexity:  O(√n)
    Space Complexity: O(1)
    """
    if n % 2 != 0:          # ① 奇数なら偶数約数はゼロ
        return 0

    m: int = n // 2         # ② 全単射により n//2 の約数カウントに帰着
    count: int = 0

    i: int = 1
    while i * i <= m:       # ③ i=1 から √m まで走査
        if m % i == 0:
            count += 1 if i * i == m else 2  # 完全平方→+1, それ以外→+2
        i += 1

    return count


if __name__ == "__main__":
    fptr = open(os.environ["OUTPUT_PATH"], "w")
    t: int = int(input().strip())
    for _ in range(t):
        fptr.write(str(divisors(int(input().strip()))) + "\n")
    fptr.close()

計算量分析

アプローチ 時間 空間 備考
✅ 全単射 + √走査 O(√n) O(1) 本実装。数学的に最適。
全約数列挙 + フィルタ O(√n) O(1) 同等だが定数倍わずかに大
線形スキャン O(n) O(1) 大 n では TLE リスク

エッジケースと検証

n 偶数約数 m=n/2 の約数 期待値 ポイント
1 0 ✅ 奇数の基底ケース
2 2 1 1 ✅ 最小偶数
9 0 ✅ 奇数(サンプル)
8 2, 4, 8 1, 2, 4 3 ✅ サンプル入力
16 2,4,8,16 1,2,4,8 4 ✅ 2 の冪
36 2,4,6,12,18,36 1,2,3,6,9,18 6 ✅ 完全平方 n=36(√n=6)

FAQ

Q1. なぜ偶数約数の個数 = n/2 の約数の個数?

写像 φ: d→d/2 が全単射になるためです。d|n かつ 2|d ⟺ d/2|n/2 が成立します。

Q2. 完全平方のとき count+=1 とする理由は?

i²=m のとき i=m/i なので同一の約数を 2 回数えてしまいます。加算を 1 に留めることで重複を防ぎます。

Q3. n が奇数のとき偶数約数がゼロになるのはなぜ?

d|n かつ 2|d と仮定すると 2|n が言えます。これは n が奇数という仮定に矛盾します。

Q4. 非常に大きな n でも動作するか?

Python の int は任意精度整数なので桁あふれはありません。n=10¹² でも √(n/2)≈7×10⁵ ステップ程度です。