ABC271 C問題の考え方
ABC271 C - Manga の考え方を書きました。
この記事は思考の流れから(ミスを含め)書いています。
答えにジャンプしたい方は こちらへ
問題の要約
- N個の自然数が与えられる
- 「2つを取り除き、代わりに任意の自然数を1つ追加する」という操作を何回でも行ってよい
- 例:
{1, 3, 4}
から3と4を取り除き、代わりに2を加えて{1, 2}
- 例:
- 「1〜M までの全ての数が存在する」という状態を目指すとき、実現可能なMの最大値を返せ
- 例:
{1 2 4 6 7 271}
なら、[1 2 3 4]
は実現可能だが[1 2 3 4 5]
は不可能。よって答えは 4
- 例:
1 <= N <= 3*10^5
かつ 与えられる自然数は10^9以下
ちなみに、実際の問題文は “マンガを1巻からどこまで読めるか” というストーリーになっていました。
全(10^9)巻のマンガということで、
- 1分で1冊ペースでも読み終わるのに1900年以上かかる
- そもそも最適ケースでも全体の1%も読めない
など、楽しい設定になっています。
印象:「大きい数、不要そう」
入力例1は以下の通りです。
6
1 2 4 6 7 271
この例では、6と271、または7と271を取り除く(売る)ことになります。また、どちらの場合でも4までしか使えません(読めません)。
これを見ていると、「大きい数ほど、要らないんじゃない?」という気がしてきました。
一旦、この考え方でよいか確かめてみましょう。
方針は以下の通りです。
- 入力を昇順ソートし、dequeに格納する。
- dequeの先頭が、次に読む巻か?
- Yes : 取り出す(読んだことにする)。
- No : dequeの末尾から2つ取り除く(売る)。そして、次に読む巻をdequeの先頭に格納する(購入して本棚へ)
入力例1なら、以下のような動作となります。
ここまで読んだ:0 / 本棚:[1 2 4 6 7 271]
ここまで読んだ:1 / 本棚:[2 4 6 7 271]
ここまで読んだ:2 / 本棚:[4 6 7 271]
ここまで読んだ:2 / 本棚:[4 6] / 売却:(7 271)
ここまで読んだ:2 / 本棚:[4 6] / 購入:(3)
ここまで読んだ:2 / 本棚:[3 4 6]
ここまで読んだ:3 / 本棚:[4 6]
ここまで読んだ:4 / 本棚:[6]
重複があったらどうする?
入力例2を見ると… 1がたくさんありました。
10
1 1 1 1 1 1 1 1 1 1
この問題には、”重複なし”という制約はありません。同じ数字(同じ巻)を複数持っている可能性があります。
これではキューが詰まってしまいます。
ここまで読んだ:1 / 本棚:[1 1 1 1 1 1 1 1 1]
既に読んだ巻が本棚にあった場合は、売ることにしましょう。 売る時点では、次にどの巻を買うべきか判断することができません。 なので、1冊1円で売って財布にしまうことにします。
ここまで読んだ:1 / 本棚:[1 1 1 1 1 1 1 1 1]
ここまで読んだ:1 / 本棚:[1 1 1 1 1 1 1 1] / 財布:1円
ここまで読んだ:1 / 本棚:[1 1 1 1 1 1 1] / 財布:2円
...
ここまで読んだ:1 / 本棚:[] / 財布:9円
そして、本棚が空でも、財布に2円以上あるならば、2円を支払って次に読む本を1冊購入することにします。
ここまで読んだ:1 / 本棚:[] / 財布:9円
ここまで読んだ:1 / 本棚:[2] / 財布:7円
ここまで読んだ:2 / 本棚:[] / 財布:7円
ここまで読んだ:2 / 本棚:[3] / 財布:5円
ここまで読んだ:3 / 本棚:[] / 財布:5円
ここまで読んだ:3 / 本棚:[4] / 財布:3円
ここまで読んだ:4 / 本棚:[] / 財布:3円
ここまで読んだ:4 / 本棚:[5] / 財布:1円
ここまで読んだ:5 / 本棚:[] / 財布:1円
少なくともサンプル入出力については、すべて正解することができるようになりました。
これでACできるでしょうか…?
WAケースがある
読んでいない巻が重複所持しているケースとして、次の入力を考えてみます。
5
1 3 3 4 9
上で考えたアルゴリズムを適用します。
ここまで読んだ:0 / 本棚:[1 3 3 4 9]
ここまで読んだ:1 / 本棚:[3 3 4 9]
ここまで読んだ:1 / 本棚:[3 3] / 売却:(4 9)
ここまで読んだ:1 / 本棚:[3 3] / 購入:2
ここまで読んだ:1 / 本棚:[2 3 3]
ここまで読んだ:2 / 本棚:[3 3]
ここまで読んだ:3 / 本棚:[3]
ここまで読んだ:3 / 本棚:[] / 財布:1円
しかし、よく考えてみると、 4 9
を売る代わりに 3 9
を売れば、 4巻まで読むことができるのです。
ここまで読んだ:0 / 本棚:[1 3 3 4 9]
ここまで読んだ:1 / 本棚:[3 3 4 9]
ここまで読んだ:1 / 本棚:[3 4] / 売却:(3 9) # ここで3と9を売る
ここまで読んだ:1 / 本棚:[3 4] / 購入:2
ここまで読んだ:1 / 本棚:[2 3 4]
ここまで読んだ:2 / 本棚:[3 4]
ここまで読んだ:3 / 本棚:[4]
ここまで読んだ:4 / 本棚:[] # 4まで読めた
先に重複の対処をする
「大きい数、不要そう」という印象は間違ってはいませんが、「どれくらい大きいと不要なのか」が曖昧でした。
例えば N=6 のとき、最大で読める巻数は 1 2 3 4 5 6
より 6 です。
7巻以降はどう頑張っても読めないので、一般に、Nより大きい巻は全て不要と言えます。
一方で、N以下の巻は、他の本の状況によって不要だったり必要だったりします。
ところで、N以下の巻でも、初期状態で不要と確定できるものがあります。
重複している巻です。
同じ巻を複数所持している場合、まず最初に、1冊のみを残して他を売ってしまいましょう。
後はこれまでの方針の通り、大きい数(後ろの巻)から売って不足している巻を買うという戦略を取ります。
アルゴリズム
- まず、重複している巻をすべて売却する。
- 残った巻を昇順ソートし、dequeに格納する。
- 以下を、”dequeが空かつ所持金が2円未満”となるまでループする。
- dequeの先頭が次に読む巻?
- それを取り出し、読んだことにする。次のループへ
- 所持金が2円以上?
- 所持金から2円を引き、次の巻を購入して読んだことにする。次のループへ
- それ以外の場合
- dequeの末尾から1つ取り除き、売却したことにする(所持金に1円を足す)。次のループへ
- dequeの先頭が次に読む巻?
動作例
本棚:[1 3 3 4 9]
本棚:[1 3 4 9] / 財布:1円
# ここからループ
ここまで読んだ:0 / 本棚:[1 3 4 9] / 財布:1円
ここまで読んだ:1 / 本棚:[3 4 9] / 財布:1円
ここまで読んだ:1 / 本棚:[3 4] / 財布:2円 # 9を売却
ここまで読んだ:1 / 本棚:[2 3 4] / 財布:0円 # 2を購入
ここまで読んだ:2 / 本棚:[3 4] / 財布:0円
ここまで読んだ:3 / 本棚:[4] / 財布:0円
ここまで読んだ:4 / 本棚:[] / 財布:0円
サンプルコード
Pythonの場合
from collections import deque
N = int(input())
A = list(set(map(int, input().split()))) # 重複排除済みリスト
A.sort()
A = deque(A)
# 所持金の初期値 = 売却した冊数 = 最初に持っていた冊数 - 現在持っている冊数
wallet = N - len(A)
c = 0 # ここまで読んだ
while True:
if A and A[0] == c + 1:
c = A.popleft()
elif wallet >= 2:
wallet -= 2
c += 1
elif A:
A.pop()
wallet += 1
else:
break
print(c)
Rubyの場合
N = gets.to_i
A = gets.split.map(&:to_i).uniq.sort # 重複排除済みリスト
# 所持金の初期値 = 売却した冊数 = 最初に持っていた冊数 - 現在持っている冊数
wallet = N - A.size
c = 0 # ここまで読んだ
loop do
if A[0] == c + 1
c = A.shift
elsif wallet >= 2
wallet -= 2
c += 1
elsif A.size > 0
A.pop
wallet += 1
else
break
end
end
puts c
回答例
- Python : 実行時間 257 ms
- https://atcoder.jp/contests/abc271/submissions/35384802
- Ruby : 実行時間 224 ms
- https://atcoder.jp/contests/abc271/submissions/36172799