garakutagoya

興味、関心のあったこと、そして私の気持ちなどを残していきたい

Atcoder Begginer Contest 029_D_1

水色の問題.桁DPの立て方をしっかり学べてよい.

atcoder.jp

問題の説明

1 \le k \le Nを満たすすべての整数kに含まれる'1'の個数はいくつか答えよ.

制約

  •  1 \le N \lt 10^{9}

考えた事

まずNの制約に注目したい.上限が10^{9}であり,k1からNまで実際に動かして1の個数を 数え上げるO(N \log k)の解法は厳しく見える.こういった場合に有効なのが桁DPである.

桁DPについては garakutagoya.hatenablog.com

でも記載した通りである.dp_{i,j}:=(i桁目まで見た時にN未満確定かどうか(jはbool値))をベースに条件を付随する.

今回もそれを使う.こうする事で計算量を桁数まで落とせる.計算量を落とせる理由としてはN=352である場合に,10,1210,12として記録するのではなく,「2桁目まで見た時にN未満確定かつ1の個数が1個」として一緒くたの遷移元として扱えるからである.一般に配列のデータへのアクセスはO(1)で行えるため,要素数を減らせばそれだけ計算量を落とせるという算段である.1 という事であらためてDP式はdp_{i,j,k}:=(i桁目まで見た時にN未満確定かどうか(jはbool値)かつ1の個数がk個ある)となる.後は遷移の仕方を考えればいい.

桁DPの遷移のさせ方は動かす数と注目している数との大小関係によって場合分けすると見通しがいい.

rep(i, num.size()) {
        ll kazu = num[i] - '0';
        rep(p, 10) {
            if (p < kazu) {
                rep(j, 2) {
                    rep(k, 12) {
                        if (p == 1) {
                            dp[i + 1][1][k + 1] += dp[i][j][k];
                        } else {
                            dp[i + 1][1][k] += dp[i][j][k];
                        }
                    }
                }
            }

上のコードの断片を見てもらいたい.数の取り方は0~910通りあるが,それとNのそれぞれの桁の値とを順に比較している.2 この場合はpNのある桁より小さい時を表している.

この時,この桁より下の桁がどんなものであっても,それがN以上にはならない.たとえばN=1287の時,11...となる数は下二桁が何であってもこれはN=1287に到達しないのだ.したがって遷移先はj=1(N未満確定)となる.

kについてはp=1の時は1の個数がひとつ増えるため,それに気をつければいい.

else if (p == kazu) {
                rep(j, 2) {
                    rep(k, 12) {
                        if (p == 1) {
                            dp[i + 1][j][k + 1] += dp[i][j][k];
                        } else {
                            dp[i + 1][j][k] += dp[i][j][k];
                        }
                    }
                }

pNの注目している桁の数字が同じ時はどうすればいいか.この場合はpN未満確定させる力はないため,前の数字に依存する.例を挙げるとN=2342のとき,13...N未満確定だが23...N未満確定とはならないという事だ.したがって遷移先も前のものがN未満確定かどうかに依存する.

else {
                rep(k, 12) {
                    if (p == 1) {
                        dp[i + 1][1][k + 1] += dp[i][1][k];
                    } else {
                        dp[i + 1][1][k] += dp[i][1][k];
                    }
                }
            }

pNの注目している桁の数字より大きい場合はどうすればいいか.この時は遷移元を絞る必要がある.N=236534のとき,24...となるものは有り得ない.したがって,14...となるもののみを考えればいい.このとき明らかに遷移元,遷移先共にN未満確定となる.

以上のようにdp_{i,j,k}を操作する事で解が得られる.聞かれているものは1の個数であるから,答えの計算はk×(dp_{ketasu,1,k}+dp_{ketasu,0,k})を足し合わせるといいだろう.ここまでお読みいただきありがとうございました.

プログラム

#include <bits/stdc++.h>
using namespace std;
#include <atcoder/all>
using namespace atcoder;
using ll = long long;
using vll = vector<long long>;
using vvll = vector<vll>;
using vvvll = vector<vvll>;
#define rep(i, n) for (ll i = (0); i < (ll)(n); i++)
int main() {
    ll N;
    cin >> N;
    string num = to_string(N);
    vvvll dp(15, vvll(2, vll(15, 0)));
    dp[0][0][0] = 1;
    rep(i, num.size()) {
        ll kazu = num[i] - '0';
        rep(p, 10) {
            if (p < kazu) {
                rep(j, 2) {
                    rep(k, 12) {
                        if (p == 1) {
                            dp[i + 1][1][k + 1] += dp[i][j][k];
                        } else {
                            dp[i + 1][1][k] += dp[i][j][k];
                        }
                    }
                }
            } else if (p == kazu) {
                rep(j, 2) {
                    rep(k, 12) {
                        if (p == 1) {
                            dp[i + 1][j][k + 1] += dp[i][j][k];
                        } else {
                            dp[i + 1][j][k] += dp[i][j][k];
                        }
                    }
                }
            } else {
                rep(k, 12) {
                    if (p == 1) {
                        dp[i + 1][1][k + 1] += dp[i][1][k];
                    } else {
                        dp[i + 1][1][k] += dp[i][1][k];
                    }
                }
            }
        }
    }
    ll ans = 0;
    rep(j, 2) {
        rep(k, num.size() + 1) { ans += k * dp[num.size()][j][k]; }
    }
    cout << ans << endl;
}

  1. 蟻本の個数制限付き部分和問題の箇所の「bool値を載せるだけのDPにはむだが多い」という話に近い.

  2. numはNをto_string函数で文字列化したもの