問題:AtCoder Beginner Contest 115: D - Christmas
公式解説PDF:https://img.atcoder.jp/abc115/editorial.pdf
公式解説動画:なし
有志解説動画:【競技プログラミング】AtCoder Beginner Contest 115 D問題をJuliaで解く - YouTube
解説
考察問題であり、数学です。
方針としては、「レベルiバーガーの下からX層にあるパティPの総数」を とし、その漸化式を導くことで問題を解きます。
とりあえず各レベルバーガーを書いてみます。わかりやすさのためパティを赤色にしています。
レベル | バーガー | 層の総数 | パティの総数 |
---|---|---|---|
0 | P | 1 | 1 |
1 | BPPPB | 5 | 3 |
2 | BBPPPBPBPPPBB | 13 | 7 |
3 | BBBPPPBPBPPPBBPBBPPPBPBPPPBBB | 29 | 15 |
レベル L バーガー (L≥1) とは、バン 1 枚、レベル L−1 バーガー、パティ 1 枚、レベル L−1 バーガー、バン 1 枚、をこの順に下から積み重ねたものである。
という性質が重要で、この性質から、
- レベル2バーガーにはレベル1バーガーが2つ存在している
- レベル3バーガーにはレベル2バーガーが2つ存在している
- レベル4バーガーにはレベル3バーガーが2つ存在している
- ...
という性質を頭にいれておきます。
レベルNバーガーの層(パティとバン)の総数をとします。はいくらでしょうか?
レベルNバーガーは、「レベルN-1バーガーを2つ+ 2枚のバン + 1枚のパティ」でできます。よって、
です。は奇数であることもわかります。
レベルNバーガーのパティの総数をとします。はいくらでしょうか?
レベルNバーガーは、「レベルN-1バーガーのパティの数*2 + 1枚のパティ」でできます。よって、
です。
今、解きたい問題は「ルンルンがレベルNバーガーのX層を下から食べる。食べたパティの枚数はなにか?」です。これは言い換えれば「レベルiバーガーの下からX層にあるパティPの総数」を求めることと同値です。
「レベルiバーガーの下からX層にあるパティPの総数」を とします。
例を列挙すると、
- ...
です。
さて、戦略としてはの漸化式を立てることです。
レベルNバーガーはその性質上、真ん中にP(パティ)がくることがわかっています。
また、真ん中より右側には「レベルN-1バーガー + バン」ということがわかります。
また、真ん中より左側には「レベルN-1バーガー + バン」ということがわかります。
ということで、3つの場合分けをすればなんかいけそうだとあたりがつきます。
- Xが真ん中より右側のとき
- Xが真ん中のとき
- Xが真ん中より左側のとき
の3パターンで漸化式を立てます。
は必ず奇数より、真ん中のPは右からの位置にあります。
わかりやすくするため、真ん中の位置をとします。
すると漸化式は、
- i = 0のとき
$$ f(0, X) = \left\{ \begin{array}{} 1 & ( X \geqq 1 ) \\ 0 & ( X < 1 ) \end{array} \right. $$
- i >= 1 のとき
$$ f(i, X) = \left\{ \begin{array}{} f(i-1, X-1) & ( X < median_i ) (Xが右側) \\ p_{i-1} + 1 & ( X = median_i ) (Xが真ん中) \\ p_{i-1} + 1 + f(i-1, X-median_i) & ( X > median_i ) (Xが左側) \end{array} \right. $$
となります。
なぜこうなるのか?というのはまあ、以下のような例をお絵かきしてみて法則性を見つけ出します。
あとはこれを解くプログラムを書きます。
ちなみに、
と一般項を変形できるらしいので、こちらを使うことで高速化できます。
コーナーケース
- 特になし
実装1(再帰関数)
# -*- coding:utf-8 -*- import sys sys.setrecursionlimit(10000) # 再帰関数のRecursionError対策 def solve(): N, X = list(map(int, sys.stdin.readline().split())) As = [1] # レベルiバーガーの厚さ(層の総数)(必ず奇数) Ps = [1] # レベルiバーガーのパティの総数 for i in range(N): As.append(As[i]*2 + 3) # レベルが1上がると、総数は2倍+3になる Ps.append(Ps[i]*2 + 1) # レベルが1上がると、パティの数は2倍+1になる def f(n, x): """レベルnバーガーの下からx層食べたときの、食べたパティの総数""" if n == 0: return 0 if x <= 0 else 1 median = (As[n]+1)//2 if x < median: return f(n-1, x-1) elif x == median: return Ps[n-1] + 1 elif x > median: return Ps[n-1] + 1 + f(n-1, x-median) ans = f(N, X) print(ans) if __name__ == "__main__": solve()
計算量はです 。
実装2(DP)
漸化式を解くといったらDPなので、DPで解こうとしたら入力例3でMemoryErrorが起きました。Xの取りうる値がでかすぎてリストの作成でコケます。以下のプログラムではACできません。
# -*- coding:utf-8 -*- import sys def solve(): N, X = list(map(int, sys.stdin.readline().split())) As = [1] # レベルiバーガーの厚さ(層の総数)(必ず奇数) Ps = [1] # レベルiバーガーのパティの総数 for i in range(N): As.append(As[i]*2 + 3) # レベルが1上がると、総数は2倍+3になる Ps.append(Ps[i]*2 + 1) # レベルが1上がると、パティの数は2倍+1になる # dp[i][x] := レベルiバーガーの下からx層に含まれているパティの総数 dp = [[0]*(X+1) for _ in range(N+1)] dp[0][0] = 0 for i in range(1, X+1): dp[0][i] = 1 # 漸化式を解く for i in range(1, N+1): median = (As[i]+1)//2 for x in range(X+1): if x < median: dp[i][x] = dp[i-1][x-1] elif x == median: dp[i][x] = Ps[i-1] + 1 else: dp[i][x] = Ps[i-1] + 1 + dp[i-1][x-median] print(dp[N][X]) if __name__ == "__main__": solve()