目次
AtCoder Beginner Contest 441 (Promotion of Engineer Guild Fes)F,G問題メモ
F - Must Buy
問題文
- ある店に $N$ 個の商品が売られています。
- 商品 $i$ $(1\leq i\leq N)$ の値段は $P_i$ 円で、価値は $V_i$ です。どの商品も $1$ つずつしか存在しません。
- 高橋君は、値段の合計が $M$ 円以下となる範囲でいくつかの商品を選びます。
- このとき、$1\leq i\leq N$ それぞれについて、商品 $i$ が以下の $3$ つの分類のどれに該当するか判定してください。
- 分類 A: $M$ 円以下で選んだ商品の価値の合計を最大にするには、その商品を必ず選ばなければならない。
- 分類 B: $M$ 円以下で選んだ商品の価値の合計を最大にするには、その商品を選んでも選ばなくてもよい。
- 分類 C: $M$ 円以下で選んだ商品の価値の合計を最大にするには、その商品を決して選んではならない。
制約
- $1\leq N\leq 1000$
- $1\leq M\leq 5\times 10^4$
- $1\leq P_i\leq M$
- $1\leq V_i\leq 10^9$
- 入力はすべて整数
解法
ナップサック問題の応用。
まずは、何を示せば「その商品を必ず選ばなければならない」と言えるのか、をちゃんと理解しないと始まらない。
以下の①②を考えて、
- ①商品 $i$ を選ばなかった場合の最大価値
- $i$ 以外の $N-1$ 個の商品から、$M$ 円以下で選ぶ時の最大価値
- ②商品 $i$ を選ぶ場合の最大価値
- $i$ 以外の $N-1$ 個の商品から、$M-P_i$ 円以下で選ぶ時の最大価値 $+V_i$
①<②ならA、①=②ならB、①>②ならCとなる。
しかし、ナップサックは「全体のDPを求めておいて、後から $i$ を選ばなかったことにする」みたいなことはできない。
当然、$i$ ごとに毎回 $O(N)$ かけるわけにもいかない。
代わりに、前からと後ろからのDPを保存しておいて、「$1~i-1$ の結果と $i+1~N$ の結果をマージする」という解法が使える。
前計算として、以下を $i=N,N-1,...$ の順に求めておく。
- $\mathrm{DP_{backward}}[i,j]:=i~N$ のアイテムを使って、$j$ 円以下で達成できる最大価値
$i=1,2,...,N$ の順に、以下のDPを計算していく。
- $\mathrm{DP_{forward}}[i,j]:=1~i$ のアイテムを使って、$j$ 円以下で達成できる最大価値
それと同時に、$i$ について答えを求める。
- ①
- $j=0~M$ について、$\mathrm{DP_{forward}}[i-1,j] + \mathrm{DP_{backward}}[i+1,M-j]$ の最大値
- ②
- $j=0~M-P_i$ について、$\mathrm{DP_{forward}}[i-1,j] + \mathrm{DP_{backward}}[i+1,M-j-P_i]$ の最大値 $+ V_i$
前後からのDPの計算、および答えを求めるパート、いずれも $i$ ごとに $O(M)$ なので、全体 $O(NM)$ で求められる。
いつもナップサックは破壊的更新で解いていたので、 途中も全て記録する $(i,j)$ 2次元のナップサックをおこなったとき、 一部の $j$ について更新し忘れが発生して、時間ロスした。
$j=0,1,...,M-P_i$ について以下のように更新するとき
- $\mathrm{DP_{backward}}[i,j+P_i] =\max(\mathrm{DP_{backward}}[i,j+P_i], \mathrm{DP_{backward}}[i+1,j]+V_i)$
$j=0,1,...,P_i-1$ については、$i+1$ のままの値をちゃんと継承しなきゃいけない。忘れがち。
- $\mathrm{DP_{backward}}[i,j] = \mathrm{DP_{backward}}[i+1,j]$
G - Takoyaki and Flip
問題文
- $N$ 枚の皿が左から右に一直線状に並んでいます。
- はじめ、どの皿も表を上にして置かれており、どの皿にもたこ焼きが置かれていません。
- 次の $3$ 種類のクエリを合計 $Q$ 回処理してください。
- タイプ $1$:整数 $L,R,X$ が与えられる。
- $i=L,L+1,\ldots,R$ に対して、皿 $i$ が表を上にして置かれている時のみ、皿 $i$ にたこ焼きを $X$ 個置く。
- タイプ $2$:整数 $L,R$ が与えられる。
- $i=L,L+1,\ldots,R$ に対して、皿 $i$ にたこ焼きが $1$ 個以上置かれていた場合、皿 $i$ に置かれているたこ焼きをすべて食べる。その後、皿 $i$ をひっくり返す。
- タイプ $3$:整数 $L,R$ が与えられる。
- 皿 $L,$ 皿 $L+1,\ldots,$ 皿 $R$ にわたる、置かれているたこ焼きの個数の最大値を出力する。
制約
- $1\le N\le2\times10 ^ 5$
- $1\le Q\le2\times10 ^ 5$
- すべてのクエリにおいて、$1\le L\le R\le N$
- タイプ $1$ のクエリにおいて、$1\le X\le10 ^ 9$
- タイプ $3$ のクエリが存在する。
- 入力はすべて整数
解法
実行制限時間の制約が厳しめだった。(Pythonは、AC提出がほぼ全てCodon)
想定外の解法の防止などもあるのかもしれないが、もうちょっと長くしてくれると嬉しい。。。3秒でも良くない?
範囲更新クエリがいっぱいあるので、とりあえず遅延セグ木に乗るかどうかをまず検討する。
その結果、やっぱり乗るのでよかったね、という問題。
クエリ1,2を処理するには、各区間を表すセグ木のノードにどのような情報を持たせれば良いか?を考える。
- 全体として
- クエリ3に答えるために、区間内の「たこ焼きの最大値」は必要
- クエリ1
- 区間内の表の皿に一律に $X$ 個のたこ焼きを追加する
- 表の皿が1枚でもあれば、最大値は $X$ 増える。0枚なら増えない。
- →「表の皿の枚数」があれば適切に処理できる。
- クエリ2
- 「たこ焼きの最大値」は一律 $0$ になる。
- 「表の皿の枚数」を適切に更新するため、「区間長」が必要となる。
よって、(たこ焼き最大値, 表の皿の枚数, 区間長)があればよさそう。
作用素同士のマージも考える。
クエリ2が来たら、たこ焼きは全て消えるのでそれまでのクエリ1は全て意味が無くなる。重要なのは、
- クエリ2が来たかどうか。来た場合は回数の偶奇
- 最後のクエリ2が来てからの、クエリ1による $X$ の総和
作用素はこの2つで表せば、少し複雑なものの作用素同士のマージも計算できる。

