Grenache's blog

競技プログラミング初心者です。

競プロに強くなりたい方、蟻本以外のいいもの見つけました。 すでに強い方は当然知っているようなことでしょうが、なかなか初心者から抜け出せない私にとってはいいなと思った本です。
  1. 「数え上げの問題」に強くなる
  2. 「マスター・オブ・場合の数」をおすすめします。
    「数え上げの問題」って、なかなかいい参考書がないと思っていました。数え上げ問題に出会うたびに、解説を読んで勉強しますが、なかなか解けるようになりません。知識としていろいろ知っておかなければいけないというよりは、テクニックやノウハウ的な部分が大きいからなのかと思います。しかし、それらにも典型というのはあって、それを知っているのと知らないのでは大きく違うようです。
    この本は、典型的な問題がカテゴリ別に整理してあり、解法が詳しく記載されています。また、ちゃんと問題を解くと、同じ系統の問題を何度も練習することになり、いい訓練になりました。解法が1つだけでなく、異なる解法についても説明があるのも良かったです。
    「数え上げ問題」に苦手意識がある方には、ぜひおすすめです。
  3. 「整数」に強くなる
  4. 「マスター・オブ・整数」まあまあおすすめです。
    数え上げの問題を競プロで考える場合は、整数に関することも知っていなければなりません。いわゆる初等整数論になるのかと思いますが、こちらの方は公式、定理など知識が結構必要になります。ただ、インターネット上や参考になる本も多めなので、困った際に勉強でもいいのかもしれません。
    この本はこれらを系統だてて勉強することができるので、効率よく学べます。内容的には初等整数論の初歩みたいな感じで、ProjectEulerを解くにはちょっと足りないですが、競プロ始めてすぐにやっとけばよかったと思いました。それに、実際に手で問題を何度も解くというのは、本質的なイメージをつかむのにはよかったです。拡張ユークリッドの互除法を使って手計算で何問も解いたりとか。
    こちらの本は、競プロ始めて数学をどう勉強しようという方に、まずはこれから入るのをおすすめします。
お気づきのようにこれらの本は「大学への数学」の別冊みたいなものです。つまり、高校生向けです。きっと競プロに強い人は高校生(あるいは中学生)の時点でこんなのはクリアしていたんだろうなと、いまさらながら差を思い知りました。
ちなみにこれらの本は高校数学の美しい物語のサイトで参考書として紹介されていたので知りました。

AtCoder のAC数と yukicoder のAC数を足し合わせてTop20ランキングを作成しました。ユーザ名はAtCoderのユーザIDになっています。
AtCoderのIDとyukicoderのIDのマッチングで間違いがあったら、ご指摘ください。
AtCoderのAC数はAtCoder Problemsからいただいています。
(Top20に変更。160704)

問題概要

問題文はこちらを参照してください。

コンテスト後の解説放送中に話題に出たDPを使った解法を書きました。数値が$n$以下という制限がないのでいわゆる桁DPというよりは、「AtCoder Typical DP Contest Q 連結」の解説のような1文字ずつ増やしていくDPが近いのかなと思います。また、$n$番目の組み合わせを求めるというのは、「AtCoder Typical DP Contest G 辞書順」の解説に出てくるやり方です。

解法

まずは作問者の方の解説に書いてある条件をもとに、この問題を「"3"と"5"からなる長さ$1~L$の文字列のうち、1桁目が"5"であり、各桁の数字の和が3の倍数であるような文字列の組み合わせの中で、$n$番目の数を求めよ」と読みかえます。

漸化式の定義: $$dp[i+1][l][(k + d[l])\mod 3]+=dp[i][j][k]$$ $$d[l]=\{3, 5\}$$

$dp[i][j][k]$:$i$文字目(1桁目から数える)が$d[j]$という数のときにそれまでの各桁の数字の和を3で割った余りが$k$である文字列の組み合わせの数
$(0\le i \le 25,\ 0\le j,l \le 1,\ 0\le k \le 2)$

初期値:$dp[1][1][2]=1$(1文字目が"5"のとき、つまり3で割った余りが2のときのみ存在する)

求める解:$dp[i][j][0]$。$i$文字目が$d[j]$のときに各桁の数字の和を3で割った余りが0である組合せの数となる。

このように$i$文字目の状態から$l$のループを回して$i+1$文字目として"3"または"5"のときそれぞれの遷移を配るDPで行います。今回はサンプルより最大の文字列の長さが25とわかるので、$i=25$までのDPを求めておくことができます。

まずは$n$番目の数が何桁なのかを求めるために、$n$から$dp[i][j][0]$を小さいほうから順に引いていきます。そしてそれが0以下になるところ(小さいほうからの累積和が初めて$n$以上になるところ)が求める桁数になります。この結果は作問者の方の解説では数え上げによって求められていたものと同じです。

yukicoder294-1

桁数と先頭の数がわかったら、その中で$n'$番目の数は何なのかを求めます。上記の例では結局、4文字目が"5"で長さが4の条件を満たす文字列(2つある)の中で1番目の組み合わせを求めることになります。これは4文字目から1文字目までどのような遷移をたどってきたかを戻りながら求める$n'$番目の数を決定していきます。遷移を戻っていくためには決定した数字によって3で割った余りが変化していくところを考えていく必要があります。

yukicoder294-2

考察

この解説ではわかりやすいかと思って$i$文字目が$d[j]$("3"または"5")のときの組み合わせの数という状態を作りました。ちなみにuwiさんの回答では$j$に関する状態は作られていません($dp[i][k]$:$i$文字目までで各桁の数字の和を3で割った余りが$k$の組み合わせの数)。その代り$i$文字目がどちらなのかを決定する際にDPの$i-1$の値を使って決定されています。この解説では$i$文字目を決定するためにDPの$i$の値を使っています。結局やっていることは一緒だと思います。

このページのトップヘ