OxOmisosiruのブログ

競プロ関連の記事を書くと思います。多分。

ABC115-D Christmasを救いたい

 Christmas 聞け

 

かの有名な(?)ABC(AtCoder Beginner Contest)115-D Christmas

全僕を苦しめた問題の一つです。

↓問題リンク

https://atcoder.jp/contests/abc115/tasks/abc115_d

その問題設定は「ダックスフンドのルンルンくんがintに収まらないほどのハンバーガーを食べる」という正気の沙汰ではないものです。

実際実験してみるとレベル1のハンバーガーはたったの5段(本当に 「たった」 か?)ですが制約上最大であるレベル $50$ はなんと $4503599627370493$ 段ものハンバーガーからなっています。ねずみ算怖すぎ。ありえねえ。

 

これだけでくそなぞなぞができそうです。

Q.レベル50のハンバーガーは4503599627370493段あるよ。これなーんだ?

A.そんなもの、あったらやばいだろ・・・

 

はい。

 

ところで、そんな問題ですが最近ようやくACすることができました。非常に嬉しかったです。

そこで自分なりの考察と解説をここにのこそうと思います。(この記事より優れた解説記事は多くあるので、それらを参考にした方がいいと思います。)

 

【ぱっと見の方針】

 レベル $50$ のハンバーガー、やばいのでハンバーガーの層をしたから全て見ることはまず不可能です。

そこで以下のようなことを考えます。

 

f:id:OxOmisosiru:20200616200251p:plain

右上のルンルンくん、かわいいですね。 

非常に明瞭な絵なので解説は不要でしょう。(は?)

結局何が言いたいかというと、

レベル $N$ バーガーは、レベル $N-1$ バーガーを、

レベル $N-1$ バーガーは、レベル $N-2$ バーガーを、

・・・

レベル $2$ バーガーは、レベル $1$ バーガーを、

含んでいるということです。

 よって、$X$ 段目までのパティの数を求めたいのに、位置が特定できない時は、今見ているレベルより下のレベルのバーガーに注目することで位置を絞り込むことができ、最終的には位置が特定でき、何枚パティを食べるかも特定することができる。ということになります。

ここまでくればあとは再帰なりで解くことができますね!ようやく光が見えました!(僕は再帰で解きましたが他の解き方もあるかもしれません。)

 

 さて、本番はここからです。

前提として各レベルのバーガーの層の数、パティの数は事前に計算しているものとします。

f:id:OxOmisosiru:20200616202250p:plain

この絵も非常にわかりやすいですね!(は?)

$(1),(2),(3),(4),(5)$ はそれぞれ下からX層目まで食べるときの、$X$ の値による場合分けを表しています。

それぞれ、以下のような状況です。

 

  • (1) $X == 1$
  • (2) $1 <  X <$ (レベルN-1バーガーの層の数)$+2$
  • (3) $X ==$ (レベルN-1バーガーの層の数)$+2$
  • (4) (レベルN-1バーガーの層の数)$+2 < X < $(レベルNバーガーの層の数)
  • (5) $X == $(レベルNバーガーの層の数)

 

 

なんとなくわかりやすくなった感がありますね。

この中で一発でパティの数を特定できるものは、$(1)と(3)と(5)$ です。

 

$(1)$ についてはレベル $0$ バーガーを見ている時ならば $1$ 枚パティを食べることができます。

それ以外では食べることができません。

$(3)$ についてはレベル $N-1$ バーガーに含まれるパティ $+1$ 枚を食べることができます。

$(5)$ についてはレベル $N$ バーガーに含まれるパティ全てを食べることができます。

これは以下の様に実装できます。

 

  1. //level.at(L)はレベルLバーガーの層の数
  2. //paty.at(L)はレベルLバーガーに含まれるパティの数
  1. if(L == 0){
  2. if(X == 1) ans++;
  3. return ans;
  4. }
  5. else if(1+level.at(L-1) == X){
  6. //真ん中のパティ一枚手前まで=レベルL-1バーガーのパティを全て取れる
  7. ans += paty.at(L-1);
  8. return ans;
  9. }
  10. else if(X == level.at(L-1)+2){
  11. //真ん中のパティまでちょうど取れる
  12. ans += paty.at(L-1)+1;
  13. return ans;
  14. }
  15. else if(X == level.at(L)){
  16. //レベルLバーガーのパティを全て食べられる
  17. ans += paty.at(L);
  18. return ans;
  19. }

 

 さて、$(2)$ と $(4)$ の場合が少し大変です。

$(2)$ の場合から考えてみましょう。

  • $(2) 1 <  X < $(レベルN-1バーガーの層の数)$+2$

という条件を言い換えてみます。

$(2)$ の条件下において $X$ はレベル $N-1$ バーガーの層の数以下に収まります。

今見ているレベル $N$ バーガーの前半部分の層の数は $1+$ (レベルN-1バーガーの層の数)として表せるので、レベルを一つ下げて、さらに、$X$ を $X-1$ として再帰関数を呼べば、レベル $N-1$ バーガーを純粋に考えることができます。

これで $(2)$ の場合は考えることができました。

 

最後に $(4)$ の場合です。

  • $(4)$ (レベルN-1バーガーの層の数)$+2 < X < $(レベルNバーガーの層の数)

(2)と同様に考えます。

Xは、今見ているレベル $N$ バーガーの前半部分を完全にカバーしています。よって、前半部分の、(レベル $N-1$ バーガーのパティの個数)+(真ん中の $1$ パティ)は確実に取ることができます。

後半部分の層の数は前半部分と同じく(レベル $N-1$ バーガーの層の数)$+1$ として表せます。よって、レベルを一つ下げて、さらに、$X$ を $X-$ (レベル $N-1$ バーガーの層の数)$-2$として再帰関数を呼べば、後半部分のレベル $N-1$ バーガーを純粋に考えることができます。

 

 

あとは実装です。$(2),(4)$ は以下の様に実装できます。

 

  1. if(1 < X && X < 1+level.at(L-1)){
  2. X--;
  3. dfs(X,L-1);
  4. }
  5. else if(X > level.at(L-1)+2 && X < level.at(L)){
  6. ans += paty.at(L-1)+1;
  7. X -= level.at(L-1)+2;
  8. dfs(X,L-1);
  9. }

 

くぅ~、疲れました!w

これにてChristmas、ACです!

 

ACコード↓

https://atcoder.jp/contests/abc115/submissions/14204942

 

ありがとうございました。