ABC461 F - Total Product is N
総乗はN
考え方
実際に数列を全部列挙するのは計算量的に非常に大変。
(……と思いきや、実はC++でなら工夫次第でどうにか間に合う。別解参照)
積が $N$ になるものを求める問題だが、実はそのものに限らず積が $N$ の約数になるものを求めるとよい。
約数を何個選んだかごとに、以下を求める。
積が $N$ の約数にならないものはスキップする。
- 選んだ約数の積で $N$ を割った商一覧
- 各商それぞれ、その商になるパターン数
- 各商それぞれ、その商になる単調増加数列のスコア合計
これは、使える約数を $1$ つずつ増やしながら、動的計画法でデータ構築していけばよい。
入力例1での動作参照。
構築出来たら、商が $1$ になったものに限り、スコアの処理をする。
使った約数の個数の階乗をかけることで、単調増加でない順の分もまとめて合計すれば答えとなる。
計算量の見積もりが難しいが、雑に見て $O(約数の個数^2\times 積で約数を作る平均長さ)$ くらい。
$10^{10}$ 以下だと、最も大変なもので $2304\times 2304\times 10くらい?$ なので間に合う。
入力例1での動作
入力を受け取る。
n: 8
$N$ の約数を全列挙する。
d: {1, 2, 4, 8}
(残り, パターン数, 昇順限定でのスコア合計) を約数の個数ごとに管理するデータを用意する。
最初に、空集合を選んだ場合として、$(N,1,0)$ を入れておく。
0 個選ぶ場合: {(8,1,0)}
1 個選ぶ場合:
2 個選ぶ場合:
3 個選ぶ場合:
4 個選ぶ場合:
$1$ を選んだ場合のデータを追加する。
0 個選ぶ場合: {(8,1,0)}
1 個選ぶ場合: {(8,1,1)}
2 個選ぶ場合:
3 個選ぶ場合:
4 個選ぶ場合:
$2$ を選んだ場合のデータを追加する。
各配列はマージソート的な処理で「残り」の値の昇順に保つ。
0 個選ぶ場合: {(8,1,0)}
1 個選ぶ場合: {(4,1,2), (8,1,1)}
2 個選ぶ場合: {(4,1,3)}
3 個選ぶ場合:
4 個選ぶ場合:
$4$ を選んだ場合のデータを追加する。
0 個選ぶ場合: {(8,1,0)}
1 個選ぶ場合: {(2,1,4), (4,1,2), (8,1,1)}
2 個選ぶ場合: {(1,1,6), (2,1,5), (4,1,3)}
3 個選ぶ場合: {(1,1,7)}
4 個選ぶ場合:
$8$ を選んだ場合のデータを追加する。
残りを $8$ で割り切れないものはスキップする。
$\{1,8\}$ と $\{2,4\}$ は同じ個数で同じ残りになるので、パターン数とスコア合計を足す。
つまり、$(1,1,9)$ と $(1,1,6)$ をあわせて $(1,2,15)$ とする。
0 個選ぶ場合: {(8,1,0)}
1 個選ぶ場合: {(1,1,8), (2,1,4), (4,1,2), (8,1,1)}
2 個選ぶ場合: {(1,2,15), (2,1,5), (4,1,3)}
3 個選ぶ場合: {(1,1,7)}
4 個選ぶ場合:
最終的に、残りが $1$ になっているものだけを答えに加算する。
昇順限定で数えているため、選んだ個数の階乗倍をする。
1 個選ぶ場合: 8 * 1! = 8
2 個選ぶ場合: 15 * 2! = 30
3 個選ぶ場合: 7 * 3! = 42
result: 80
注意点
入力値 $N$ やその約数は、int 型からはみ出る。
long long 型を用いること。
答えは $998244353$ で割った余りを要求されているので、剰余類環の考えに従って処理する。
何か足し算や掛け算をするたびに結果を % 998244353 する。
別解
積が $N$ になる $2$ 以上の要素からなる数列を、深さ優先探索で列挙してもよい。
計算量がかなり厳しいが、C++なら工夫すれば間に合う。
まず、$1$ を含む数列を列挙から外す。
$1$ がないデータから簡単に再現できるし、再現するまでもなく和に反映できる。
次に、単調減少数列のみ探す。
最初に選んだ数ではやめに残りに使える数を減らし、二分探索で次の約数のループ範囲を狭く絞る。
単調減少でないものは、数列の長さの階乗をかけることでまとめて反映できる。
これら $2$ つの工夫をすると、数列列挙でどうにか間に合う。