AtCoder Beginner Contest 114: D - 756
問題
問題文
https://atcoder.jp/contests/abc114/tasks/abc114_d
問題概要
整数 が与えられる. の約数のうち, 約数がちょうど 75 個となる数はいくつあるか.
解答例
指針
- N! を素因数分解する
解説
約数がちょうど 75個となる整数とは より p, q, r を異なる素数とすると
のいずれかである.
を のように素因数分解し, から上記の条件に当てはまる整数がいくつあるか考える.
素因数分解をするには, まず以下のようにして n 以下の素数を列挙する. 下記の実装では n 以下の素数の数を返すようにしている.
int primes[100]; int get_primes(int n) { int ret = 0; for (int i = 2; i <= n; i++) { bool flag = true; for (int j = 2; j <= i; j++) { if (i % j == 0 && i != j) { flag = false; break; } } if (flag) { primes[ret++] = i; } } return ret; }
素数列挙のアルゴリズムとしては計算量はあまりよくないが, 今回は制約が と小さいので, 気にしない.
ちなみに素数列挙といえば, ABC084 の D問題が想起される.
素因数分解は以下のように 2~N を素数で順々に割っていけば良い.
int main() { ... int exps[100] = {0}; for (int i = 2; i <= n; i++) { int k = i; for (int j = 0; j < cnt; j++) { while (k % primes[j] == 0) { exps[j]++; k /= primes[j]; } } } ... }
素因数分解がきちんとできているか以下のコードを実行して確かめる.
#include <iostream> using namespace std; int primes[100]; int get_primes(int n) { int ret = 0; for (int i = 2; i <= n; i++) { bool flag = true; for (int j = 2; j <= i; j++) { if (i % j == 0 && i != j) { flag = false; break; } } if (flag) { primes[ret++] = i; } } return ret; } int main() { int n, cnt; int exps[100] = {0}; cin >> n; cnt = get_primes(n); for (int i = 2; i <= n; i++) { int k = i; for (int j = 0; j < cnt; j++) { while (k % primes[j] == 0) { exps[j]++; k /= primes[j]; } } } for (int i = 0; i < cnt; i++) { cout << primes[i] << " " << exps[i] << endl; } return 0; }
- 実行結果
$ ./a.out 100 2 97 3 48 5 24 7 16 11 9 13 7 17 5 19 5 23 4 29 3 31 3 37 2 41 2 43 2 47 2 53 1 59 1 61 1 67 1 71 1 73 1 79 1 83 1 89 1 97 1
よさそう.
, のときは以下のように二重ループを回して条件に合うものをカウントすればいい.
for (int i = 0; i < cnt; i++) { for (int j = 0; j < cnt; j++) { if (i == j) { continue; } // p^2 * q^24 if (exps[i] >= 2 && exps[j] >= 24) { ans++; } // p^4 * q^14 if (exps[i] >= 4 && exps[j] >= 14) { ans++; } } }
のときが少し厄介で, 以下のようにしてはだめ.
// p^2 * q^4 * r^4 // だめな例 for (int i = 0; i < cnt; i++) { for (int j = 0; j < cnt; j++) { for (int k = 0; k < cnt; k++) { if (i == j || i == k || j == k) { continue; } if (exps[i] >= 2 && exps[j] >= 4 && exps[k] >= 4) { ans++; } } } }
なぜなら, j と k が入れ替わるようなパターンを別のものとしてカウントしているからである. 例えば, (52 * 24 * 34) をカウントするときを考えれば二重にカウントしているのが分かる.
j と k 入れ替わらないように for を回せばよい.
- C++ による実装例
/* submission: https://atcoder.jp/contests/abc114/submissions/3829582 Language: C++14 (GCC 5.4.1) time: 1 ms Memory: 256 KB */ #include <iostream> using namespace std; int primes[100]; int get_primes(int n) { int ret = 0; for (int i = 2; i <= n; i++) { bool flag = true; for (int j = 2; j <= i; j++) { if (i % j == 0 && i != j) { flag = false; break; } } if (flag) { primes[ret++] = i; } } return ret; } int main() { int n, cnt, ans = 0; int exps[100] = {0}; cin >> n; cnt = get_primes(n); for (int i = 2; i <= n; i++) { int k = i; for (int j = 0; j < cnt; j++) { while (k % primes[j] == 0) { exps[j]++; k /= primes[j]; } } } // p^74 for (int i = 0; i < cnt; i++) { if (exps[i] >= 74) { ans++; } } for (int i = 0; i < cnt; i++) { for (int j = 0; j < cnt; j++) { if (i == j) { continue; } // p^2 * q^24 if (exps[i] >= 2 && exps[j] >= 24) { ans++; } // p^4 * q^14 if (exps[i] >= 4 && exps[j] >= 14) { ans++; } } } // p^2 * q^4 * r^4 for (int i = 0; i < cnt; i++) { for (int j = 0; j < cnt; j++) { for (int k = j+1; k < cnt; k++) { if (i == j || i == k || j == k) { continue; } if (exps[i] >= 2 && exps[j] >= 4 && exps[k] >= 4) { ans++; } } } } cout << ans << endl; return 0; }
- Python3 による実装例
"""submission: https://atcoder.jp/contests/abc114/submissions/3829623 Language: Python3 (3.4.3) time: 20 ms Memory: 3188 KB""" def get_primes(n): primes = [] for i in range(2, n+1): flag = True for j in range(2, i+1): if i % j == 0 and i != j: flag = False break if flag: primes += [i] return primes def main(): n = int(input()) primes = get_primes(n) cnt = len(primes) exps = [0] * cnt for i in range(2, n+1): k = i for j in range(cnt): while k % primes[j] == 0: exps[j] += 1 k /= primes[j] ans = 0 for i in range(cnt): if exps[i] >= 74: ans += 1 for i in range(cnt): for j in range(cnt): if i == j: continue if exps[i] >= 2 and exps[j] >= 24: ans += 1 if exps[i] >= 4 and exps[j] >= 14: ans += 1 for i in range(cnt): for j in range(cnt): for k in range(j+1, cnt): if i == j or i == k or j == k: continue if exps[i] >= 2 and exps[j] >= 4 and exps[k] >= 4: ans += 1 print(ans) main()
DP による解法
を のように素因数分解したあと, 以下の条件に当てはまるものを考える.
以下のような DP テーブルを定義する.
dp[i][x] := a[i] までを使ったとき, 約数の和が x になるようなものの個数.
a[cnt] までを使って約数の和が 75 となる dp[cnt][75] が求める答えである.
- C++ による実装例
/* submission: https://atcoder.jp/contests/abc114/submissions/3829705 Language: C++14 (GCC 5.4.1) time: 1 ms Memory: 256 KB */ #include <iostream> using namespace std; int primes[100]; int get_primes(int n) { int ret = 0; for (int i = 2; i <= n; i++) { bool flag = true; for (int j = 2; j <= i; j++) { if (i % j == 0 && i != j) { flag = false; break; } } if (flag) { primes[ret++] = i; } } return ret; } int main() { int n, cnt, ans = 0; int exps[100] = {0}; cin >> n; cnt = get_primes(n); for (int i = 2; i <= n; i++) { int k = i; for (int j = 0; j < cnt; j++) { while (k % primes[j] == 0) { exps[j]++; k /= primes[j]; } } } int dp[100][76] = {0}; dp[0][1] = 1; for (int i = 0; i < cnt; i++) { for (int j = 0; j <= 75; j++) { for (int k = 0; k <= exps[i]; k++) { if (j * (k + 1) > 75) { break; } dp[i + 1][j * (k + 1)] += dp[i][j]; } } } cout << dp[cnt][75] << endl; return 0; }