問題
$T$ 個のクエリが与えられる。各クエリでは正の整数 $N$ が与えられるので、$1$ 以上 $N$ 以下の整数のうち 約数の個数が最大 のものについて、その約数の個数 $d$ と、そのような整数のうち最小のもの $x$ を出力せよ。
入力形式
T
N_1
...
N_T
制約
| # | 条件 |
|---|---|
| 1 | $1\le T\le 2\times10^5$ |
| 2 | $1\le N_i\le 10^{18}$ |
| 3 | 入力はすべて整数 |
入出力例
入力例1
4
1
10
100
1000000000000000000出力例1
1 1
4 6
12 60
103680 897612484786617600($N=10$: 約数 4 個の $6,8,10$ のうち最小の $6$。$N=100$: 約数 12 個の $60,72,84,90,96$ のうち最小の $60$)
背景と出典
約数の個数が「それより小さいどの正整数よりも多い」数を 高度合成数(highly composite number) と呼ぶ。S. Ramanujan, "Highly Composite Numbers", *Proceedings of the London Mathematical Society* (2) 14, 1915 が体系的に調べ、その素因数の指数が広義単調減少であることなどを示した(OEIS A002182)。本問の答え $x$ は「$N$ 以下で最大の高度合成数」そのもの。本セッションでは論文・OEIS を開いておらず、正しさの根拠は下の検証ログ($10^6$ までの全数と 5001 件一致)。
$10^{18}$ 以下での最大の約数の個数 103680($x=897612484786617600$)や、$10^9$ 以下で 1344($x=735134400$)は、競技プログラミングで「約数列挙の最悪ケース」の見積もりに使われる値で、本解の出力もこれと一致した(検証ログ)。
概念図:高度合成数の指数
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$x=p_1^{e_1}p_2^{e_2}\cdots$ なら約数の個数は $(e_1+1)(e_2+1)\cdots$ で、どの素数か には依らない。同じ指数の組なら、なるべく小さい素数を使った方が $x$ が小さい。
ヒント2(アプローチ)
答えの $x$ は $2^{e_1}3^{e_2}5^{e_3}\cdots$(連続する素数)で $e_1\ge e_2\ge e_3\ge\cdots$ の形に限ってよい。もし $e_i<e_{i+1}$ なら 2 つの指数を入れ替えると約数の個数はそのままで値が小さくなるから。$2\cdot3\cdots47\le10^{18}<2\cdot3\cdots53$ なので、使う素数は 47 まで。
ヒント3(誘導)
この形の $x\le10^{18}$ を DFS で全部列挙すると 32,749 個しかない。どのクエリでも答えはこの中にあるので、$x$ の昇順に並べて「ここまでの約数の個数の最大(同数なら $x$ 最小)」の累積を作り、各クエリは $N$ 以下の最後の候補を二分探索で引く。
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
from bisect import bisect_right
LIMIT = 10**18
PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] # 2*3*...*47 <= 10^18 < 2*3*...*53
def candidates():
"""x = 2^e1 3^e2 5^e3 ...(e1 >= e2 >= ... >= 0)で x <= LIMIT のものを (x, 約数の個数) で全列挙"""
res = []
def dfs(i, x, d, max_e):
res.append((x, d))
if i == len(PRIMES):
return
p = PRIMES[i]
for e in range(1, max_e + 1):
if x > LIMIT // p: # x * p > LIMIT
break
x *= p
dfs(i + 1, x, d * (e + 1), e) # 次の素数の指数は e 以下
dfs(0, 1, 1, 64)
res.sort()
return res
def main():
cand = candidates()
xs = []
best = [] # best[j] = 小さい方から j 番目までの候補での (最大の約数の個数, その最小の x)
bd, bx = 0, 0
for x, d in cand:
if d > bd: # 同数なら先に出た(小さい)x を残すので >(>= にしない)
bd, bx = d, x
xs.append(x)
best.append((bd, bx))
data = sys.stdin.buffer.read().split()
t = int(data[0])
out = []
for i in range(t):
j = bisect_right(xs, int(data[1 + i])) - 1 # x = 1 が必ず候補にあるので j >= 0
d, x = best[j]
out.append(f"{d} {x}")
print("\n".join(out))
main()
計算量: 候補の列挙 $K=32749$ 個で $O(K\log K)$、クエリ 1 件 $O(\log K)$。実測(Python 3.14.7、M1 Mac)で候補の列挙 0.02 s、$T=2\times10^5$ 全体で 0.33 s。
Step-by-Step 解説
$x=\prod p_i^{e_i}$ の約数は各素数の指数を $0..e_i$ から独立に選ぶので $\prod(e_i+1)$ 個。
素数 $p<q$ に指数 $a<b$ が付いているとき、入れ替えて $p^bq^a$ にすると約数の個数は同じで、$p^bq^a/(p^aq^b)=(p/q)^{b-a}<1$ なので値は小さくなる。よって「$N$ 以下で約数最大、かつ最小」の $x$ は、指数が大きい順に小さい素数に付いている。同じ理由で、使う素数は 2 から隙間なく連続する($q$ を使って $p<q$ を使わないなら $q$ を $p$ に替えればよい)。
Step 2 の形の数($\le10^{18}$)の集合は $N$ と無関係に決まり、どの $N$ の答えもこの中にある。だから 1 回だけ列挙して、全クエリで使い回す。
候補を $x$ の昇順に並べ、「先頭から $j$ 番目までで約数の個数が最大のもの(同数なら先に出た=小さい $x$)」を累積で持つ。$N$ 以下の最後の候補の位置を bisect_right で求めれば答え。同数のとき >= で更新すると大きい $x$ に置き換わってしまう。
$2\cdot3\cdot5\cdots47\approx6.1\times10^{17}\le10^{18}$、$\times53$ で $10^{18}$ を超える。よって 16 個目の素数 53 は使えない。上限を小さく取りすぎると(例: 23 まで)大きい $N$ で答えを誤る(検算 2019 件中 2006 件、実測)。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| $1..N$ の約数の個数を篩で数える | $O(N\log N)$ | 検算用のみ | $N=10^{18}$ では不可能。$10^6$ までの検算に使った |
| クエリごとに $N$ を上限に DFS | 1 件 5.6 ms | 不採用 | 正しいが $T=1000$ で 5.62 s(実測)。$T=2\times10^5$ では間に合わない。検算には使った(2019 件一致) |
| 指数の単調性を使わず全素数・全指数を DFS | 候補が爆発 | 不採用 | Step 2 の性質を使わないと、$10^{18}$ 以下の全 smooth 数を列挙することになる |
| 使う素数を 23 までにする | 候補が少ない | 不採用 | $N\approx10^{18}$ 付近で 2019 件中 2006 件誤答(実測) |
| 候補の全列挙+累積最大+二分探索(本解) | $O(K\log K+T\log K)$ | 採用 | $10^6$ まで全数と 5001 件・クエリごと DFS と 2019 件一致 |
前提が変わったら:
- 「約数の和」が最大 なら値が指数だけでは決まらない(素数そのものに依存する)。superabundant 数(約数の和 $/x$ が最大)は別の性質を持つので、指数の単調性を改めて確かめてから同じ全列挙を使う
- $N\le10^{36}$ なら候補数が増え、素数も 83 付近まで要る。候補数を実測してから表を作るか、クエリごとの DFS に戻すかを決める
- 「約数の個数がちょうど $D$ の最小の数」 なら、$D$ の約数分解($D=\prod(e_i+1)$ の並べ方)を DFS する別問題になる
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 同数のとき大きい $x$ を残す | 累積で >= を使う | > で比べ、先に出た小さい $x$ を残す(2019 件中 636 件・$10^6$ までの 5001 件中 1864 件誤答) |
x * p <= LIMIT を掛け算で確かめる | Python では問題ないが、C++ などではあふれる | x > LIMIT // p で比べる癖をつける |
| 使う素数を少なく取りすぎる | 素数の積の上限を見積もらない | 47 まで($2\cdots47\le10^{18}<2\cdots53$) |
| 指数の上限を小さく取る | $2^{59}<10^{18}<2^{60}$ を忘れる | 最初の素数 2 の指数は 59 まで回る(本解は 64 を上限にして LIMIT // p で止める) |
次のステップ
- 発展1: 「$N$ 以下の約数の個数の最大値」だけでなく、「約数の個数がちょうど最大値になる $N$ 以下の整数の個数」を求める(候補の外にも同数の数がある。入力例の $N=100$ では 5 個)
- 発展2: 「約数の個数が $D$ 以上の最小の数」を答える。候補の表を約数の個数で引く逆向きのクエリになる
- 次回予告: 焦点帯 M1(DP・ゲーム)に戻る
検証ログ(本セッションで実行した数値)
- 全数との一致(実測): $10^6$ までの約数の個数を篩で数え、$N=1..2000$ 全部と $[1,10^6]$ の乱数 3000 点、$N=10^6$ の計 5001 件でクエリごと DFS 版・本解とも全件一致
- $10^{18}$ 付近(実測): $[1,10^{18}]$ の乱数 2000 点と $N=10^0..10^{18}$ の 19 点、計 2019 件で本解とクエリごと DFS 版(素数 53 まで)が全件一致
- 代表値(実測): $N=10^9$ → 1344 735134400、$N=10^{12}$ → 6720 963761198400、$N=10^{18}$ → 103680 897612484786617600、$N=897612484786617599$ → 98304 748010403988848000
- 誤った版(実測): 素数 23 まで → 2019 件中 2006 件誤答($10^6$ までの 5001 件では誤りなし)、累積に
>=→ 2019 件中 636 件・5001 件中 1864 件誤答 - 実行時間(実測): 候補 32,749 個の列挙 0.02 s、$T=2\times10^5$ 全体 0.33 s。クエリごと DFS は $T=1000$ で 5.62 s
- 扱っていない範囲: Ramanujan の論文・OEIS A002182 との照合、約数の和(superabundant)版