yutasの競技プログラミング勉強帖

競技プログラミングの問題についての解説記事を主に書いています。

C - Repunit Trio / トヨタ自動車プログラミングコンテスト2023#8 (AtCoder Beginner Contest 333)

問題

10進法ですべての桁の数字が  1 である整数をレピュニットと呼ぶ。( 1, 11, 111, \cdots がレピュニットになる。)

ちょうど3つのレピュニットの和として表せる整数のうち、  n 番目に小さいものを求めよ。

入力

整数  n が1行に与えられる。

条件

  • 実行時間制限: 2s
  • メモリ制限: 1024MB
  •  1 \leq n \leq 333

出力

答えを1行に出力すること。

解法

整数  i(ただし、  i \geq 1)に対して、  i 桁のレピュニットは  11 \cdots 1 i 桁)です。

従って、和に使われるすべてのレピュニットが  i 桁以下のとき、最大値は3つすべてが  i 桁のレピュニットのときの、  33 \cdots 3 i 桁)です。 この値は  i 桁であるので、  i + 1 桁のレピュニットよりも小さい値となります。

このことから、ある整数  i に対して、  i 桁以下の3つのレピュニットの和として表せる整数の個数の合計が  n を上回ったら、 i + 1 桁以降のレピュニットについて考慮する必要はありません。

ここで、「  i 桁以下のレピュニットを3つ用いて得られる和の値」の個数について考えます。 3つのレピュニットの桁数をそれぞれ  a, b, c とします。(ただし、  1 \leq a \leq b \leq c \leq i

このとき、 a, b, c については  a \lt b + 1 \lt c + 2 であるので、 (a, b + 1, c + 2) は相異なる3つの整数ということになります。

 1 \leq a かつ  c + 2 \leq i + 2 であることから、 (a, b + 1, c + 2) の組み合わせの総数は  1 以上  i + 2 以下の整数を3つ相異なるようにとる組み合わせの総数と等しいので、 \begin{align} \frac{(i + 2) \{ (i + 2) - 1\} \{ (i + 2) - 2 \}}{3 !} = \frac{1}{6} i (i + 1) (i + 2) \end{align} が、和の値の個数となります。

ここで、  i = 12 としてみると \begin{align} \frac{1}{6} \cdot 12 \cdot 13 \cdot 14 = 364 \end{align} となります。

今回の問題では  n は最大でも  333 であるので、  12 桁以下のレピュニットの和について調べ上げれば、十分に問題の解答が得られるということが言えます。

以上から、  12 桁以下のすべてのレピュニットについて、 forループを3重で回して和を計算することで、  n 番目の値を求めることができます。

ここで、 {10}^{i} - 1 = 99 \cdots 9 i 桁)であるので、  i 桁のレピュニットは \begin{align} \frac{1}{9} ({10}^{i} - 1) \end{align} と表すことができます。

コードを書く際はこれを利用します。

ソースコード

int n;
vector<ll> ans; // ans は和として得られる値を保存する vector

int main() {
  cin >> n; // 値の入力

  // i <= j <= k となるように3重のforループを回し、和を列挙する
  for (int i = 1; i <= 12; i++) {
    ll num_i = (pow(10, i) - 1) / 9; // i 桁のレピュニットは (10 ^ i - 1) / 9 で与えられる
    for (int j = i; j <= 12; j++) {
      ll num_j = (pow(10, j) - 1) / 9;
      for (int k = j; k <= 12; k++) {
        ll num_k = (pow(10, k) - 1) / 9;
        ans.push_back(num_i + num_j + num_k); // 和を ans に入れる
      }
    }
  }
  sort(ans.begin(), ans.end()); // ans をソートする
  cout << ans[n - 1] << endl; // ans が 0-index になっていることに注意して出力する

  return 0;
}

感想

場合の数についての説明を書く際に、「  a \leq b \leq c のときの組み合わせの計算って、どのようにすればいいんだっけ…?」となりました。