C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で指定した桁和を持つN以下の数をカウントする方法

数字からなる文字列 str と目標の合計値 total が入力として与えられます。この記事では、str で表される数 N 以下の整数のうち、各桁の数字の合計が total と一致するものの個数を求める方法を解説します。

具体例で理解しよう

例 1

入力: N="110"、sum=5

出力: 指定した桁和を持つ N 以下の数の個数: 7

説明: 110 以下で桁の合計が 5 になる数は次の通りです。

5, 14, 23, 32, 41, 50, 104

例 2

入力: N="1000"、sum=3

出力: 指定した桁和を持つ N 以下の数の個数: 10

説明: 1000 以下で桁の合計が 3 になる数は次の通りです。

3, 12, 21, 30, 102, 111, 120, 201, 210, 300

プログラムで使用するアプローチ

このアプローチでは動的計画法(DP)を用いて、途中結果を三次元配列 arr[18][2][162] にキャッシュします。ここで、18 は最大 18 桁の数に対応し、2 は状態フラグ用の値 0 と 1、162 は全 18 桁が 9 だった場合の最大桁和(18 × 9 = 162)に対応しています。

配列の要素 arr[i][j][k] は、「先頭から i 桁分を確定させたときの、桁和が k になる数の個数」を表します。j の値は、現時点で構築中の i 桁の数が N の先頭 i 桁と一致しているかどうかを示すフラグです(j=1 なら一致、j=0 ならすでに N より小さい)。たとえば N=123 で i=2 のとき、先頭 2 桁が 12 と一致していれば j=1、そうでなければ j=0 となります。

再帰の終端では、i が N の桁数に達し、かつ累積桁和 k が入力の total と一致すれば 1 を返し、そうでなければ 0 を返します。

次の桁(i+1 桁目)を決めるときは、フラグ j の値に応じて選べる数字の範囲が変わります。

  • j=1 の場合: 先頭 i 桁が N と一致しているため、次の桁には「N の i+1 桁目以下の数字」しか選べません。これにより、構築中の数が N を超えるのを防ぎます。
  • j=0 の場合: すでに構築中の数が N より小さいことが確定しているため、次の桁には 0〜9 のどの数字を選んでも N を超えることはありません。

すべての桁を処理し終えたら、集計した個数を最終結果として返します。

アルゴリズムの手順

  • 数 N を表す文字列 str と、目標の桁和 total を受け取ります。
  • 配列 arr[18][2][162] を用意し、memset を使ってすべて -1 で初期化します(-1 は「未計算」を意味します)。
  • 関数 count_digits(int i, bool check, int temp, int total, string str, int size) が再帰的に arr[][][] を埋めながら、最終的に条件を満たす数の個数を返します。
  • 現在の桁位置 i が N の桁数と一致し、かつ現在の累積桁和 temp が total と一致する場合は 1 を、そうでなければ 0 を返します。
  • count = arr[i][check][temp] を参照し、値が -1 でなければ(計算済みであれば)その値を即座に返して重複計算を回避します。
  • 一時変数 check_2(bool 型)と temp_2(int 型)を用意します。
  • for ループで数字 '0'〜'9' を順に試します。check が 0(N と一致中)の場合は、現在の数字 ch が str[i] より大きければループを抜けます。
  • check_2 = check || ch < str[i] を設定します(一度でも N より小さくなったら、以降ずっとその状態を維持します)。
  • temp_2 = temp + (ch - '0') として新しい累積桁和を計算します。
  • count += count_digits(i + 1, check_2, temp_2, total, str, size) で再帰呼び出しを行い、結果を加算します。
  • すべての数字を試し終えたら count を返します。

実装例(C++)

#include <bits/stdc++.h>
using namespace std;
int arr[18][2][162];
int count_digits(int i, bool check, int temp, int total, string str, int size) {
    if (i == size) {
        if (temp == total) {
            return 1;
        } else {
            return 0;
        }
    }
    int count = arr[i][check][temp];
    if (count != -1) {
        return count;
    }
    count = 0;
    bool check_2;
    int temp_2;
    for (char ch = '0'; ch <= '9'; ch++) {
        if (!check) {
            if (ch > str[i]) {
                break;
            }
        }
        check_2 = check || ch < str[i];
        temp_2 = temp + (ch - '0');
        count += count_digits(i + 1, check_2, temp_2, total, str, size);
    }
    return count;
}
int main() {
    string str = "1101";
    int size = str.size();
    int total = 5;
    memset(arr, -1, sizeof(arr));
    cout << "Count of numbers smaller than or equal to N with given digit sum are: " << count_digits(0, 0, 0, total, str, size);
    return 0;
}

上記のコードを実行すると、次の出力が得られます。

出力

Count of numbers smaller than or equal to N with given digit sum are: 26

計算量

  • 時間計算量: O(桁数 × 2 × 最大桁和 × 10)。各状態について 0〜9 の数字を一度だけ試すため、非常に効率的です。
  • 空間計算量: O(桁数 × 2 × 最大桁和)。メモ化テーブルのサイズに依存します。

このように、桁 DP(桁ごとの動的計画法)を活用することで、単純な全探索では膨大な時間がかかるような大きな N に対しても、高速に答えを求めることができます。

  1. C++でN以下のジャンピングナンバーをすべて出力する方法

    この記事では、与えられた数値 n 以下のジャンピングナンバー(Jumping Numbers)をすべて出力するC++プログラムを紹介します。 ジャンピングナンバーとは? ジャンピングナンバーとは、隣り合う桁どうしの差が必ず「1」になっている数のことです。例えば、4565、98、7 などが該当します。 すべての1桁の数字(0〜9)は、ジャンピングナンバーとみなされます。 235 のように、隣接する桁の差が1以外になる箇所がある数は、ジャンピングナンバーではありません。 問題の例 具体的な入力と出力の例を見てみましょう。 入力: N = 32 出力: 0 1 2 3 4 5 6 7 8 9

  2. C++で1〜Nの数の合計がSになる最小個数を求める

    問題文1からNまでのN個の整数と、ある整数Sが与えられます。使用できる各数はN以下という制約のもとで、合計がSになるために必要な「数の個数」の最小値を求めて出力してください。例n = 7、s = 10 の場合、必要な数は最小で2個です。たとえば、次のような組み合わせが考えられます。(7, 3) (6, 4)アルゴリズム合計Sをできるだけ少ない個数で作るには、大きな数(最大でN)を優先的に使えばよいことが分かります。したがって、答えは次の式で計算できます。S % N > 0 のとき : (S / N) + 1 S % N == 0 のとき : S / Nつまり、これは「SをNで割った値の切