AtCoder Beginner Contest 114: D - 756

問題

問題文

https://atcoder.jp/contests/abc114/tasks/abc114_d

問題概要

整数  N (1 \leqq N \leqq 100) が与えられる.  N! の約数のうち, 約数がちょうど 75 個となる数はいくつあるか.

解答例

指針

解説

約数がちょうど 75個となる整数とは  75 = 3 \times 5 \times 5 より p, q, r を異なる素数とすると

  •  p^{74}

  •  p^{2} \times q^{24}

  •  p^{4} \times q^{14}

  •  p^{2} \times q^{4} \times r^{4}

のいずれかである.

 N! N! = p_1^{e_1} \times p_2^{e_2} \times p_2^{e_2} ... のように素因数分解し,  e_1, e_2, e_3 ... から上記の条件に当てはまる整数がいくつあるか考える.

素因数分解をするには, まず以下のようにして 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;
}

素数列挙のアルゴリズムとしては計算量はあまりよくないが, 今回は制約が  N \leqq 100 と小さいので, 気にしない.

ちなみに素数列挙といえば, 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

よさそう.

 p^{2} \times q^{24},  p^{4} \times q^{14} のときは以下のように二重ループを回して条件に合うものをカウントすればいい.

    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} \times q^{4} \times r^{4} のときが少し厄介で, 以下のようにしてはだめ.

    // 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 による解法

 N! N! = p_1^{e_1} \times p_2^{e_2} \times p_2^{e_2} ... \times p_k^{e_k} のように素因数分解したあと, 以下の条件に当てはまるものを考える.

{\displaystyle
\begin{eqnarray}
  \left\{
    \begin{array}{c}
        (a_1+1)(a_2+1)...(a_k+1) = 75 \\
        a_i \leqq e_i
    \end{array}
  \right.
\end{eqnarray}
}

以下のような 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;
}

参考文献