Typical DP Contest_E_数
コンテストの名前の通りDPの問題.どのようにDPテーブルを更新するかが少し特徴的なのでブログにまとめておく.新規性は1ミリもありません.
問題の説明
以下の正整数であり,十進表記したときの各桁の数の和がの倍数であるものの個数を[tex: mod 1,000,000,007 ]で求めよ.
条件
考えた事
見ての通りがとんでもなく大きい.もちろん毎に判定してcountするような解法が全く持って間に合わないのは火を見るよりも明らかだろう.
この問題に限らず,一般に調べるべきもののサイズが非常に大きいもの*1であるとき,string型に直して各桁について調べるアプローチが典型である.今回もそのようなアプローチを取る.つまり,を最大桁の数とみて,その各々の桁の情報を使って問題を解く方針だ.
今回ならどのように見て行けばいいだろうか.もちろん素直に各桁の数字を決めていって全部試す方法では意味がない.どうすべきか.
例えばであったとして,一々これをと試すのはどう考えても無駄が大きい.これを変えたいのだ.ここでDPの考えが出てくる.先にDPテーブルをどう作ればいいかを見てもらおう.
として,DPテーブルに上の条件で満たしている数の個数を書き入れていくのだ
基本はこの形である.数を上の桁から決定していくように眺めるのだ.この場合だと千の位がかのどちらかで決定する.
イメージとしては配るDPを想像してもらえばいい.上の桁(例えば)を決定していく事で,下の桁の情報が増えていく形だ.
千の位がである時,どうやってもを下回る事が確定しているので,残りの百の位,十の位,一の位での倍数となるものは千の位がとなるものから来ることが分かる.残りの桁の決定でも同様である.
千の位がであるとき,もちろん未満確定なので,
となる.明らかに桁の情報が増えたとしても元を辿れば前の桁の情報からきている.
今回であれば,各桁の合計がDで割り切れるかどうかなので,問題の条件式は各桁の合計として
となる.上の例であるならば千の位はとして進めているので,となる.としているので1桁目はのいずれしか存在しない.つまり,
の形である.
そして,上の桁が確定であるものを,,,,...に配っていくのだ.
ここでは何かというと二桁目,つまり百の位の数字であり,00~09の段階で条件を満たしているものは何個かというように見ている.
今回であると
となる.見て分かるようにさっきまで雑然としていたものが徐々にまとまってきている.
まだ終わりではない.今度は1桁目,つまり千の位が1のものを考える.千の位がであるとき,未満確定かどうかは分からないため,
となる.今度はこれを配っていく.
千の位がであり,を超えないように百の位を決めるとき,百の位の数として考えられる候補はであり,このうちは明らかに未満確定する.
,である事に注意すると,
のように配られ,結果,
となる.最後に,千の位が1,百の位が2であるものを考えると,これは未満確定ではないため,
と配られる.つまり,
となる.以上をまとめると,
となる.これは以下となるよう上2桁を決める時の候補がの計個である事と一致している.
ポイントとなるのは桁数の合計を記録するのではなく,桁数の合計をで取った数を記録している事にある.こうする事で配列の幅をとし,情報の整理と計算量削減に成功している.
乱暴な言い方かもしれないが,であるとき,とととが持つ情報量は全く同じであるから,1括りにまとめておくのが楽だというような解釈をすればいいだろう.これは楽であるだけでなく,こう1括りにまとめてしまう事で,計算量の幅を
から大きく落とし込める点でも有益である.
この纏めるというところが桁DPの真髄かもしれない.まず,前の桁を決定し問題の条件式に応じてまとめ,その次の桁にその情報(個数)を配り,またまとめ,また配りを繰り返していく.
今回であればまとめる作業は[tex:*2\bmod D]であり,これはただ剰余を求めているだけであるからで行える.本来はで一々見て,各々の桁の和について一つ一つ考えるため,計算量はであったところから比べると大きな進歩であろう.これを踏まえてコードに書き起こすと以下の通り.
初期条件として桁目でであるとき,の0桁目はであるとみなして,としている.
後はコードにある通り,上から桁を決め,Dの剰余の回数配り,新しい桁としてあり得るのはであるとしてループを回す.
計算量はとなり,制限時間に間に合う.
答えとしては,,を合計したものからを引いたものとなる.
は桁目*3まで決めて未満確定のもの()で,各桁の合計をで割った余りがとなるものの合計であり,は桁目まで決めて,未満確定でないもの(これは実質のみを指す)の中で,各桁の合計をで割った余りがとなるものの合計である.*4
を引く理由としては初期条件の部分でとしており,それが巡り巡って,の中にの個数1個分を余分に含む形となっているからである.問題文が「以下の非負整数で~」となっていた場合,この処理は行わなくていい.
いかがだっただろうか.正直,自分のプログラミングの力が足りなく,例に大きく頼った未熟な説明となってしまった.これが今の自分の限界である.
とはいえ,今の自分の力で精一杯,桁DPのアルゴリズムを説明できていると思う.これを読んだ誰かの理解の一助となる事を願っております.今回も最後までお読みいただきありがとうございました.
プログラム
AtCoder Grand Contest 007_B_Construct Sequences
青色の問題.久々にアルゴリズムもへったくれもない問題です.数学好きなのでこういった問題の方が好きです.
問題の説明
からを並べた順列が与えられる.以下の条件を満たす数列および,を出力せよ.
条件
- 正整数列は狭義単調増加
- 正整数列は狭義単調減少
- は狭義単調増加
考えた事
問題文にある通り,答え方としてはに合わせてを作りだし,答えればよい.
まず考えるべきは全探索である.極端な話であるが*1,この問題の入力としてあり得る数列全てに合わせとを先に全て作りだせば答えとなる.
この指針には2つ問題がある.まず第一に計算量である.当然のことながら,上の方法を行う場合,計算量はとなりどう考えても無理がある.
次に解法としての適当さである.そもそもこの問題は答えをつ列挙する問題である.裏を返せば全部試すなと読めるのではなかろうか.答えが複数種類ある事を暗に示唆されている問題で愚鈍に全て試すのは明らかに効率がよくない.
ではどうすべきか.こういった問題において効果的なのは条件を徐々に厳しくする形で問題を見つめる事である.全ての条件を一気に考えても分からなくなってにゃにゃ!?となっておしまいである.我々は人間だ.カワイイカワイイにゃんこと違って物事を整理して考えねばいけない種族だ.がんばろう.
この場合だと「は狭義単調増加」はめんどうそうである.ひとまず置いて置こう.他から考える.とはいえ他の条件は狭義単調増加,狭義単調減少な数列を作るだけなのですぐできるだろう.なぜわざわざすぐできる事を分けて先に書いたか.ここが今回の大きなポイントである.
つまり今回の問題はによらず,先にの数列を作っておき,に合わせてそれを微調整するように考えればいいのである.
がどの順で大きくなるかはが入力されるまで分からない.ならば先にどんな数列を作っておくとよいだろうか.自分なら先に作っておくべき数列はによらずが同じ大きさになる数列と考える.
そして後からの順にそれぞれの数列の項にずつしていけば「は狭義単調増加」の条件も満たす.ここで注意しなければならないのは,による変化量である.これを行うと一番小さいととでの差が出来る.「は狭義単調増加」という条件を満たしたままこれを行うには,先にととでの差をつけておけばいい.
つまり,といった具合だ.そしてこれによって先に作るべきの詳細も分かる.「によらずが同じ大きさになる数列」を作ればいいのだからとなるように作ればいい.
このようにして作ったとが以上である.これによって「は狭義単調増加」以外の全てを満たし,「によらずが同じ大きさになる数列」を作成できた.
後は,
として,の順にずつ差をつけていけば,元々の条件を満たしながら「は狭義単調増加」の条件をみたす.
そしてこれは高々,であり,の制約を満たしている.以上をもって数列を作成する事に成功した.
こういった問題は典型解がなく,難しいが非常に楽しいものである.コツというか自分の考えとしては条件を緩める,特にのような変数絡みの条件を後回しにすると,解放のアプローチが得やすいだろう.ここまでお読みいただきありがとうございました.
プログラム
*1:極端とつけている時点で察しはつくかもしれない
第12回日本情報オリンピック 予選(過去問)_D_ 暑い日々 (Hot days)
DPの問題.前日の情報をどうするか,そこがカギとなります.
問題の説明
温度条件が度の下で日服を選んだ場合の派手さの差の絶対値の合計の最大値を求めよ.
条件
考えた事
派手さの差の絶対値の合計を考える.つまり前の情報を遷移させる必然性を伺わせる事から,この問題の解法としてその場その場で最適な服を選び続けるGreedyかDPが思いつくだろう.
事実としてこの問題はGreedyに近い発想でも解く事が出来る*1.
この記事にあるように,温度条件を満たしている服の中で1日毎に派手さが最大と最小のものを取り,前の日に選んだ服との派手さの差が大きくなる方を順々に選び続ければいいとなる.
上の方法が成立する理由は,差の絶対値を足し続ける手法であるため,基本端っこと端っこ(最大と最小)を選び続ける方が差の絶対値が大きくなるからである.この場合の計算量はであり,非常に高速である.
ただ,残念ながら自分はそれが思いつかなかった.ポンコツやね.ではどうするか.愚直にDPをしていこう.また手順に則ってやっていく.
ステップ1.全探索出来るかどうか
全探索する場合の計算量の見積もりをしよう.とりあえず服を全部選べると仮定した場合が都合がよい*2.
この場合,枚の服から1枚を選ぶことを日繰り返すため,計算量はとなる.
指数時間,の上限は,当然全探索では間に合いません.
ステップ2.状態の遷移を考える
この問題の場合,前日に選んだ服と今日選んだ服の派手さの差の絶対値をそこまでの派手さの差の絶対値の合計に足し合わせる事で遷移するだろう.
ステップ3.変数を見つけ,配列の表を書く
遷移する変数は何になるだろうか.自分は最初,日数,前日に選んだ服,当日の服を変数にするといいと考えた.三次元DPである.この場合,服を選べない場合と同じ服しか選べない場合を差別化するために初期値はで設定した.
注意したいのは1日目で,この時にはいずれにせよ派手さの差の絶対値の合計値ゼロである.しかし,温度条件を満たしている服と満たしていない服が存在するため,前者には,後者はそのままとした.
もちろんこの発想は間違っていない.ただ図に起こしてみるとどうであろうか.
これは入力例1を図に起こしたものである.が前日に選んだもの(存在しない場合は),がその日に選んだものである.
見ての通り遷移がややこしい.なぜか.となるからである.ある日に選んだものが次の日に影響するため,このようにswapする.だからややこしいのである.
また,のような後に生きない無駄なデータがある.そしてどのデータが無駄か必要か判別するのもややこしい.使うデータを判別するならば列の中の最大値を使えばよい(前々日に選んだ服はどうでもいいため).しかし毎回毎回それを調べるのも億劫である.
そして自分は何がしたかったのか0以上のデータを判別してからこれをやろうとしたため,ややこしすぎて実装出来なくなってしまった.さてどうしようか.
ステップ4.漸化式の作成
大事なのは変数である.上の中でいらない変数はなんであろうか.答えは昨日選んだ服である.なぜいらないか.理由は簡単である,もし服を選べない場合,そこはとなっているため,昨日の服の選択の如何に関わらず,遷移する中での派手さの合計の最大値を求めさえすれば,問題は起きないという事だ.
何が言いたいかというと上の図のを全て圧縮して以下の図のようにすればいい.
を全て圧縮して二次元DPとし,にはに全てを足し,その最大値だけを記録していけばよいとなる.ポイントとなるのは初期値であり,初期値がであるからこそ,前日に選べた服と選べなかった服を差別化できる.一回の派手さの差の絶対値を足したとて,はのまま.一々「前の日に使えた服は何だっけ~、ううううう.」などと悩まなくてよいのだ.
したがって変数は2つでよい.一応もう少ししっかり書くと,の時,
となるだろう.については服の数だけループを回して全て調べあげればよい.
もちろん条件を満たしていない場合,となるため,一々書く必要もない.
今回の場合,「最高気温が天気予報に従ったときに着るのに適した服が,日間のどの日に対してもつ以上存在することが保証されている.」とあるため,必ず1つは条件を満たす.行全てがで終わる事はあり得ない事が保証されているため,このようにして考えていい.まとめると,
となる.初期条件としては
とするとよいだろう.
ステップ5.漸化式を元に配列を埋める
ここまで書けば実装は難しくない.
としてDP配列を作る.とりあえずで置いて置き,
としてを埋めればよい.
後は3重ループを回し,配列を埋めればよい.
漸化式の部分はこのようになる.計算量はとなり,高々となるため,十分間に合う.
ステップ6.求めたい配列の値を見て答えを導く
をを動かして,最大値を出力すればよい.遷移がややこしくタフな問題であった.お疲れ様でした.
プログラム
AtCoder Beginner Contest 015_D_高橋くんの苦悩
DPの問題.ナップサック問題+α.スクリーンショットの重要度ってなんやねん.
問題の説明
枚数が枚,幅がに収まる条件の下で画像を選ぶときの重要度の合計の最大値を求めよ.
条件
考えた事
枚数制限がない場合,これは典型的なナップサック問題と呼ばれるものとなる.
(競プロでは)ナップサック問題はDPで解く事が一般的である*1.
上の記事に記載の通り,DPの基本は変数の捜索にある.ナップサック問題でのDPによる使い方をかいつまんで話すと,荷物の添字をとし,重量の総和をとすると,「番目までの荷物で重量以下となるように荷物を選んだときの価値の最大値」としてテーブルを作ればよい.
今回であれば幅と重要度がそれに対応する.幅の制限を満たしながら重要度を最大とするように考えれば上のナップサック問題と何一つ変わらない.読者の皆様には簡単でしょう.
今回のポイントは枚数制限の要素が追加されている事だ.ナップサック問題でいうところの制限がもう1つ追加されている点に難しさがある.そこに注意してDPを考えてみよう.
ステップ1.全探索出来るかどうか
全探索する場合の計算量の見積もりをしよう.幅の制限は一先ず置いておき,個選び,それ以外を選ばないくらいで考えると楽でいい.
であるから計算量はとなりお察しの通り宇宙時間である.無念.
ステップ2.状態の遷移を考える
この問題の場合,前までの状態に写真を足せるかどうかで見ればよいだろう.その場合,幅,重要度はもちろんのこと写真の枚数が1枚増える事に注目するのが今回の大きなポイントである.オタクの部屋と違って普通の人は部屋に物を増やしたがらない.
ステップ3.変数を見つけ,配列の表を書く
遷移する変数を考えてみよう.この場合鍵となるのは小さい範囲で状況の変化を考えてみる事だ.もしスクリーンショットを選んだとしよう,その場合に変化するものは枚数,幅の合計,重要度の合計,そして画像を選んだという事実である.枚数,幅,選択した画像が分かっていれば重要度の合計を求める事が出来る.したがって上の3つを変数として考えてみよう.
イメージとしてはと思ってもらえれば差し支えない.
枚数を,使用可能な画像の添字を,幅の合計をとして表を書いてみる.この場合,表を一度に書くと3次元を記述する事になってしまうため,の値で分けて書くとよい*2.
入力例1を用いて作成した表を以上に載せた.遷移している部分を赤と青で記しているが,3次元であるためどこが遷移しているのかまだ難しいだろう.私自身もこれだけではがどこから来たものか分からず考えてしまった.
次のステップで遷移について考えてみよう.
ステップ4.漸化式の作成
ここからは漸化式の作成に入る.キーとなるのは状態の変化の仕方が画像を選んだ/選んでいないで二分される事だ.
まずは枚目の画像を選べない場合を仮定しよう.この場合,が条件となる.選べない場合,枚数も,幅の合計も一切変わらない.変わる部分といえば枚目を使わないという事はで考えても変わらない,をにしてもよい点だろう.
つまり,
としてよいのである.したがって,
となる.
次に枚目の画像を選べる場合を考える.もし選ばないならばの遷移は選べない場合と全く変わらない.
選ぶ場合,使用可能な枚数は枚減り,幅も減る.番目を使う事が分かっていれば,足し算の線形性と同じ画像は2度使わない制約から,後はの場合で考えてよい.そして重要度は上がる.そしてDPには選ぶ選ばないいずれかの重要度の合計の最大値を記載する事を繰り返せばいい*3.
そのため,
となる.まとめると,
となる.初期条件としては
とすると分かりやすいだろう.
ステップ5.漸化式を元に配列を埋める
実装をするにあたってどうしたらいいだろう.ここまで読んでいただけた方ならお分かりの通り,3次元配列を用いる.
としてDP配列を作る.初期条件はだけで十分であるが,(何もない)=としても齟齬は生じないためそうしている.距離コストの最小値を求める場合,この部分がになる事もあるため注意が必要だ.
後は3重ループを回し,配列を埋めればよい.
漸化式の部分はこのようになる.計算量はとなり,高々となるため,やや危ないが間に合う.
ステップ6.求めたい配列の値を見て答えを導く
を見て,それを出力すればいい.なかなかタフな問題であった.
プログラム
*1:他の解き方について知りたい方は
第2回:ナップサック問題を色々な方法で解いてみた【ブレインパッドの数理最適化ブログ】 - Platinum Data Blog by BrainPad
を参照されたい.
*2:平面毎にスライスした後,高さで積分し,立体の体積を求める問題を思い浮かべてもらうと分かりやすいだろう.大学受験ではしばしば出てくる
*3:これも線形性による
AtCoder_Beginner_Contest_240_C Jumping Takahashi
いよいよDP.最初は優しめの問題から.
問題の説明
高橋君が回または段のジャンプを行います.回のジャンプの後ににいる可能性があるか答えなさい.
条件
- 入力は全て整数.
考えた事
まず,DPとは何をする手法なのか.端的に言えば値を記憶する配列を作り,それを使い回すものであろう.この問題においてそれは出来るのだろうか.
例えば1回目に段上がり,2回目に段上がったものと,1回目に段上がり,2回目に段上がったものでは当然現在位置が異なる.Greedyと違って今回は段まで上る最適な方法は存在しない(入力によって方法を変えなければならない)ため,段に辿り着けるかどうかは全てを試さなければ分からない.
ところがこれを全探索で実装しようとするとTLEエラーとなってしまう.全てのについてとを選んだ場合を考えると計算量は乗となり,が上限では計算が間に合わない.
なぜ全探索ではダメなのか.無駄が多いのである.最後だけとで異なるが,それ以外は全く同じものを選んだ場合を考えてもらいたい.全探索ではこれを一々計算し直すため,結果として非常に遅い手法となってしまう.
これを解消するのがDPである.配列に回目までに行ける部分を記憶させる事で,回目に行ける部分は回目までに行ける部分にまたはを足せば求められる.DPがメモ化再帰と呼ばれる所以である.*1裏を返せば回の状態を記憶させる事に意味のない問題の場合,DPは不適当な解法だといえる.
状態の遷移を上の図*2に表した.回目に座標にいる場合はを,そうでない場合はを記している.回目(登り始める前)はにいるため,にのみがつき,そうでない部分はが記される.
回目のジャンプを行うとまたはのジャンプを行う.ここで重要なのは回目のジャンプで行ける先を求める場合,回までの結果にそのまままたはを足せば求められるという点だ.これが可能であるからこそ,これを帰納的に回繰り返して回目に行ける場所が求められる.
少し前の繰り返しになるが,「繰り返し」が出来るかどうかがDPの使用可否を決める.どれを変数とすれば状態を遷移させられるか,繰り返しを作る事が出来るか,もし出来るならばどのような遷移となるか,これを考える事が非常に重要である.
競技プログラミングにおいては条件のところで変数のヒントが与えられている事も多い.今回においてパラメータ(変数)はとであり,は定数である.問題文にパラメータとしては既に記されており,後は自分でを置けばDPの遷移を考えられる.この知見は大事にしたい.
さあ実装だ.DPの実装は上の配列を埋める事に終始する.配列を埋めるにはどうすればいいのか.ここで出てくるのが漸化式である.
回目までの遷移と回目の遷移を結び付ける式があれば配列を埋めて行ける.DPの最終工程はこれである.
今回であればならばとなる.それ以外はとなる.つまり,
となる.まとめてコードにすると,
となる.プログラム上はからスタートしているため,の添え字がつずれているのには注意したい.を配列のデフォルトにし,とを動かして,上の条件を満たすものだけにをつけていく.
計算量に注目するとこれはであり,全探索のと比べ,大幅に短縮されている事が分かるだろう.
今回は制限に余裕があるため行っていないが,例えばをif文に追加し,以上となった場合は考えない*3ように実装すれば計算量はとなり更に短縮できる.
DPの流れをまとめると
- 全探索出来るかどうかを考える.(計算量が大きな基準となる)
- 状態が遷移するか,前の状態から今の状態を帰納的に導き出せるかを考える.(前の状態に足せるかどうかを見ると良さげ.)
- 変数を見つけ,小さな値の範囲でいいから配列を書いてみる.
- それを基に漸化式を作る.
- 漸化式を元に変数を動かし配列を全て埋めていく.
- 求めたい値に対応する配列を見て,答えを導き出す.
となるだろう.最後はもちろん問題によって異なるが今回であればである.これがかかがジャンプで辿り着けるかどうかを決める為,それを参照して出力すればいい.
プログラム
AtCoder Beginner Contest 091_C_2D Plane 2N Points
Greedyの問題.どうマッチングさせると仲良しさんが作られるか.
問題の説明
赤い点の外側にある青い点を結ぶことで作る事の出来るペアの個数の最大値を求めよ.
ただしでかつのとき,はの外側にあるという.
条件
- 赤い点と青い点の座標,座標は全て相異なる.
考えた事
最初考えた事は,赤い点からどのように青い点を選べばペアを多く作れるかであった.そういった中で,なるべく座標の差を小さくしたかったため,赤い点の座標の小さい順から考え,ペアを作る事の出来る中で座標が最小の青い点を選ぶことを繰り返すものであった.
しかしこれでは上手くいかない.文章からも明らかであるが上の状態では座標の情報が一切反映されていない.我々が目指している事は闇雲に外側の点を選ぶのではなく,外側は外側でもなるべく内側によっている点を選ぶことである.もっというとなるべくペア間の距離を短く結ぶことにある.
なぜなら距離が離れれば離れるほどその点はより外側にあるという事になり,ペアを作る効率がよくない.どれだけ(原点から)外側にあるかの度合いを測る尺度として距離があり,その距離の長短とペアが対応している事は興味深い.
イメージにおこすとこのようになる.上の座標が最初に考えたペアの作り方である.見て分かるようにどう考えても効率的でない.
翻って下はどうであろうか.ペア間の距離が短くなっているのもさることながら,何より作成可能なペアの個数が増えている.おそらくこれを目指す事が最善手であると考えた.
ではこのようにするためにはどうすべきであろうか.自分は青い点に注目した.座標の小さい青い点から(外側から),ペアを作成可能な赤い点を探していく手法だ.
そういった中でどれとペアを結ぶべきか.やはりここでも距離が出てくる.距離の計算はで求める事が出来る.そして,今は座標が小さい方から見ているため,これ以上座標の距離を縮めようとしてもあまり距離は縮まらない.むしろ注目している青い点に対し,赤い点の座標がギリギリまで近い点を選ぶことでその距離を最小化出来る.このようにしてGreedyを行うと上手く行くのである.
「あえてはじめは距離が離れているところを選ぶのはありじゃないか.」,と思うかもしれない.しかし,今考えているのはが小さい順である事に注意したい.最初にあえて自分に近い点を選ばず,遠くの点を選んだとしよう.そうした時,次の青い点は[tex;x]座標が離れているぶんもっと遠くなる.そうなってしまった場合,その行為に何の得もない.
また,それによって次のペアの距離が縮まったと仮定しても,その場合はそもそもはじめの青い点は次のペアの赤い点とペアを結べないか,どちらの赤い点も入れ替えてペアを作る事が可能かのどちらかでしかないため,「はじめにあえて距離が離れているものを選ぶ」方針ではGreedyに対し,ペアの数を減らす事さえあっても増やす事はありえない.
加えてGreedyの基本姿勢が「その場での最善」を優先し,それを繰り返す思考であるから「その場での最善」を優先せず,かといって後の方で取り返せる保証のない方針は得策ではない.*1
考えなければならないのは一度ペアに使った点はもう使えないという事.今回は一度ペアになった赤い点の座標を大きくし,彼方に飛ばす事でそれに対応した.
青い点はループで全探索するため,ペアの成否を考えなくてよい.扱いやすい.
プログラム
AtCoder Regular Contest 006_C_積み重ね
Greedyはブログにまとめてなかったなと思ったので.ゴミ屋敷の住人必見!?
問題の説明
高橋君が個の箱を積み上げる時に出来る箱の山の最小個数を求めなさい.箱にはその重さ以下の箱を重ねる事ができる.
条件
考えた事
Greedyの基本は「その場での最善」を繰り返していく事にあります.これを意識して考えるとこの問題は分かりやすいかもしれないです.
では,この問題における「その場での最善」とは何か.結論からいうと今重ねられる山の中で,1番軽いものに重ね,重ねられない場合は山を作る事を繰り返す事となる(方針A).
ここでポイントとなるのはなるべく重ねる事である.例えば8,1,7のように積み上げるとして,8の上に1を載せるのはちょっともったいなく感じるかもしれない(方針B).しかし,それは気のせいでしかない.
上(方針A)と下(方針B)の比較を見て分かる通り,1の上に8を載せようが載せまいが出来る状況は全く同じなのである.
また,
とした場合,上の方針Aでは山が1つなのに対し,下の方針Bでは山が2つ出来てしまう.
一応下の山の方には8が残っているというメリットがあるものの,例えば次に8以下の数字が来た場合,上ならば新しい山を作り,下ならばその8の上に重ねる事で全く同じ状況*1となり,8より大きな数が来た場合は重ねられないため,どちらも山が1つずつ増えてしまい,どちらにせよ8を取っておく意味が全くない事が分かるだろう.
以上から方針Bは却下され,方針Aが最善と分かる.*2大事なのは方針Bが上振れても方針Aと変わらず,順番次第では山一つ増やしてしまう事だ.これが分かれば後は山のてっぺんの数字を記録する配列を作り,てっぺんが変わったら書き換え,新たに山が出来たらcountを増やし,てっぺんの数字を記録するプログラムを書けば容易に解けるだろう.