概要

基礎的な DP と,高速化の工夫を問う問題です.
柔らに考えると案外簡単な問題かもしれません.

問題特有の性質と,計算量の見積もりに注意してください.

問題原案:tnodino

略解

11 次元の DP を考えます.BFS 等でもよいです.
事前に全ての値について答えを求めることができるので,各クエリには定数時間で答えられます.

以降前計算について,(KK はとり得る最大の値としてよいです.)
[Easy] は愚直に約数の総和を計算すれば O(KK)O(K \sqrt K) 時間で解けます.
[Hard] に正解するためには,約数の総和の求め方を工夫する必要があります.O(KlogK)O(K \log K) 時間程度を許容します.

解説

.Ⅰ. 操作が満たす性質

正の整数 kk の正の約数の総和を SkS_k とします.

ある正の整数 kk に対して,kk は自身の正の約数として 11 および kk 自身を持ちますから,明らかに k2  k<k+1Sk\forall_{k\geq2}\;k < k+1 \leq S_k といえます.

つまり,A,B\text A, \text B いずれの値を選んで操作を行っても,xx の値は減少しません.
(11 に関して B\text B の操作を行ったときに限って S1=1S_1 = 1 より変化しませんが,操作の「最小回数」を知ることが目的なので,x=1x = 1 のときは常に A\text A を選ぶこととして問題ありません.)

.Ⅱ. 動的計画法

(動的計画法(DP)についての詳しい解説は他サイト様へ委ねます.よろしければ TGC000-G も参照ください.)

次のような DP を考えます.
ここで,KK はとり得る最大の値とすることで,全てのクエリに対しての答えを求めたことになります.

定義

  • DP[k](x\mathrm {DP}[k] \coloneqq ( xkk に等しくするために必要な操作回数の最小値))

初期状態

  • DP[1]=0\mathrm {DP}[1] = 0
  • DP[i]=(2iK)\mathrm {DP}[i] = \infty \hspace{0.3em} \scriptsize (2 \leq i \leq K)

遷移

  • For  keach[1,2,,K):\text{For}\;k \xleftarrow{\text{each}} [1, 2, \ldots, K):
    • DP[k+1]minDP[k]+1(A\mathrm {DP}[k + 1] \xleftarrow{\min} \mathrm {DP}[k] + 1 \>(\mathrm{A} の値を選ぶ場合))
    • DP[Sk]minDP[k]+1(B\mathrm {DP}[S_k] \xleftarrow{\min} \mathrm {DP}[k] + 1 \>\>\>\>\>\,\, (\mathrm{B} の値を選ぶ場合))

「実装例」の項も参照してください.

.Ⅲ. SkS_k の算出

愚直に求める (TLE)

1vk1 \leq v \leq k を満たす vv を全探索し,vvkk の約数ならば SkS_kvv を加算することで求められます.
この方法の時間計算量は O(k)O(k) ですから,DP の遷移も含めると,O(K2)O(K^2) 時間となって,[Easy] の制約下でも間に合いません.

少し工夫する ([Easy] に正解)

ある正の整数 vv について,vvkk の約数ならば,kv\frac{k}{v} も同時に kk の約数となります.
つまり,探索する vv1 vk1\ \leq v \leq \sqrt k を満たすもののみで十分とわかります.

C++ による実装例を次に示します.

C++

\\
もう少し工夫する ([Hard] に正解)

先述した方法は「1kK1 \leq k \leq K を満たすすべての kk について,1vk1 \leq v \leq \sqrt k を満たす vv を全探索し,vvkk の約数ならば SkS_kvvkv\frac{k}{v} とを加算する」というものでした.
探索対象となる vv の中には kk の約数でないものも含まれるため,無駄がありそうです.
逆に,「必ず何かの約数となる」ような値を列挙できれば,かなり効率的であると考えられます.

少し視点を変えて「1kK1 \leq k \leq K を満たす kk の倍数 mVk{ikkikK}m \in V_k \coloneqq \{\, ik\,|\,k \leq ik \leq K \,\} それぞれについて,SmS_mkk を加算する」という方法を考えてみましょう.
実際に,この方法でもすべての 1kK1 \leq k \leq K を満たす kk について,正の約数の総和を列挙できることが分かります.

kk の約数を列挙する」代わりに「kk が約数となる m(K)m \, (\leq K) を列挙する」といったイメージです.
直感的には後者の方が無駄がなく効率がよさそうですね.

これをより確実に評価してみましょう.

Vk=Kkk=Kk1|V_k| = \lfloor \frac{K-k}{k} \rfloor = \lfloor \frac{K}{k} \rfloor - 1 となり,VkV_k の要素は O(Vk)O(|V_k|) 時間で列挙できますから,1kK1 \leq k \leq K を満たすすべての kk について VkV_k の要素を列挙しようとすると 1kK(Kk1)K1kK1kKlogK\displaystyle \sum_{1 \leq k \leq K} \Big( \lfloor \frac{K}{k} \rfloor - 1 \Big) \backsimeq K \sum_{1 \leq k \leq K} \frac{1}{k} \backsimeq K \log K 回程度の計算が必要になります.

しかしこれならば [Hard] の制約 K2×106K \leq 2\times 10^6 のもとでも十分に間に合います.

なお,調和数🔗が対数程度の速度で発散するという事実を知らなくても,(プログラムや電卓を用いて)以上の(総和を用いた方の)式へ実際に K=2×106K = 2 \times 10^6 を代入して計算してみることでも 確かに間に合いそうだ ということが確かめられるはずです.

.Ⅳ. 発展

1)1) 類題

実装例

SkS_k の値は DP の遷移の前に計算してもよいですが,DP を更新しながら同時に計算していくこともできます.
こちらの方が実装が少々楽です.

C++
Python

\\

余談

調和数... どこか見覚えがありますね

ええ.まさか被るとは思いませんでした.(ABC272-E)
で,でも.この問題の原案が出たのは 8/19 時点なのでわざとじゃないですよ...??(

許してね☆