桁DPのコーディングに関する記事です. N 以下の整数で,ある条件を満たすものを数えます.

参照

以下のoptさんの記事 をもとにして,少し追加しています:

コーディングの方針

  • 配るDP
  • 表の更新を書く行は,ソース上で1箇所にする.
    • その桁に表れうる数 (0..9 とか 0..1 とか) をループで回し, 「この数を付け加えた時の格納先」を考える
    • 格納先の添字を表す変数を,格納元で初期化して適宜変更する.
  • 上位桁を 0-padding した状態で考える.
    • 0以上N以下の数を数えることになる.
    • 次のような場合は,すべてがゼロであることを表すフラグを使って対応する.
      • 「左端の桁」の概念が出てくる
      • 0以上ではなく,1以上を数える
#define REP(i, x) for (ll i = 0; i < (x); i++)

// N は各桁数値のベクトルで表現.ds[0] が最上位桁.
// 問題文の都合で string にしても良い.
vector<ll>& ds;

// DP表 tbl[eq][az][p1][p2]...
//   eq: 上限値に等しいかどうかを表すフラグ
//   az: 全部の桁がゼロ (all zero) かどうかを表すフラグ
//           p1やp2の計算に使わなければ省略して良い
//           「最上位桁」を特別に扱う場合などに必要
//   p1,p2... : 考えるべき性質 (問題に応じて変わる)
vector tbl_init(2, vector(2, vector(??, vector(??, 0LL))));
auto tbl = tbl_init;

tbl[1][1][??][??] = 1   // 初期状態は,eq=1, az=1
for (auto d : ds) {
  auto prev = move(tbl);
  tbl = tbl_init;
  REP(eq,2) REP(az,2) REP(p1,??) REP(p2,??) {
    if (prev[eq][az][p1][p2] == 0) continue; // 性能的に重要かもしれない
    REP(x,10) { // この桁で考える数
      // eq, az, p1, p2 の新しい値 new_eq, new_az, new_p1, new_p2 を計算する
      // たとえば new_p1 は,「直前の桁までで 性質 P1 の値が p1 であったとき,
      // 次の桁として x を追加すると,P1 の値が new_p1 になる」もの.
      if (eq && x > d) continue;
      ll new_eq = eq && x == d;
      ll new_az = az && x == 0;  // p1 などの計算に必要なら使う.
      ll new_p1 = ....;
      ll new_p2 = ....;
      // 最後にテーブルを更新する
      tbl[new_eq][new_az][new_p1][new_p2] += prev[eq][az][p1][p2]; 
    }}}

問題集