目次
AtCoder Beginner Contest 440 (Promotion of Engineer Guild Fes) E,F,G問題メモ
E - Cookies
問題文
- $N$ 種類のクッキーがそれぞれ $10^{100}$ 枚あります。
- $i$ 種類目のクッキーの $1$ 枚あたりの美味しさは $A_i$ です。
- これらのクッキーから合計で $K$ 枚を選びます。
- クッキーの選び方は、選んだクッキーの種類の多重集合が一致するときかつその時に限り同じとみなします。
- 全ての選び方 $\binom{N+K-1}{K}$ 通りそれぞれについて、選んだクッキーの美味しさの和を考えます。これらを大きな値から順に重複を込めて $S_1,S_2,\dots$ とするとき、$S_1,\dots,S_X$ を求めてください。
制約
- $1\leq N \leq 50$
- $1 \leq K \leq 10^5$
- $1 \leq X \leq \min\left(10^5, \binom{N+K-1}{K}\right)$
- $-10^9 \leq A_i \leq 10^9$
- 入力は全て整数
解法
Dijkstra風に、大きい方から探索する。
$A_1 \ge A_2 \ge ... $ となるように index を振り直す。
「$c_i:=$ クッキー $i$ を選んだ個数」とし、クッキーの選び方を $C=(c_1,c_2,...,c_N)$ のように $c_i$ の列で表す。
選び方に対するスコア(美味しさの和)は、$\sum_i A_i c_i$ である。
一番大きい $S_1$ の選び方は $(K,0,0,...,0)$ であり、この時 $A_1 \times K$ である。
次に大きい $S_2$ の選び方は $(K-1,1,0,...,0)$ である。
この次の $S_3$ は、$(K-2,2,0,...,0)$ と $(K-1,0,1,0,...,0)$ の2つの選択肢があるが、 しかし、この2つのいずれかに限られる。
これを繰り返し、次々と
- 暫定でスコアが最大になる選び方を、次の $S_i$ として確定させる
- その選び方の次にスコアが大きい可能性のあるものを優先度キューに入れる
として探索を広げていけば、Dijkstra と似た要領で大きい順に確定していける。
ただし、遷移が重複してはいけない。
言い換えると、選び方 $C_x$ が、選び方 $C_y$ からも $C_z$ からも
「次に大きい可能性のあるもの」として探索されてしまってはいけない。
$C_x$ のスコアが2回計上されてしまい、誤った答えになる。
単純なDijkstraであれば、探索済みの頂点番号を記録して探索済みなら遷移しないようにすればよいのだが、
今回のような、複数の値からなるものを頂点として扱う場合、
探索済みかどうかを調べるのにも時間がかかるため、始めから「重複しないような遷移方法」を決めるのが大切となる。
(今回は $N$ が小さいため、これでも間に合うようだが)
次のような法則で探索を広げるとよい。
- $c_i$ が $0$ でない最も右のindexを $i$ とする
- 以下の2通りに遷移できる。
- $c_{i-1}$ を1減らし、$c_i$ を1増やす($c_{i-1} \gt 0$ の場合)
- $c_i$ を1減らし、$c_{i+1}$ を1増やす($i \lt N$ の場合)
よって、$(score, i, c_{i-1}, c_i)$ を優先度付きキューに入れ、大きい順に取り出せば、 適切にスコアを差分更新し、次に大きい可能性のある選び方に遷移できる。
$N=1$ の時、実装によっては配列外参照を起こすので注意。
F - Egoism
問題文
- $N$ 頭の馬がおり、$1$ から $N$ までの番号が付けられています。
- 馬 $i$ ($1 \leq i \leq N$) は機嫌 $A_i$ と丁寧さ $B_i$ をもちます(ここで、$B_i$ は $1, 2$ のいずれか)。
- 夜になると、$N$ 頭の馬が順番に $1$ 回ずつ水浴びをします。$j$ 番目 ($1 \leq j \leq N$) に水浴びをする馬の番号を $p_j$ とおくと、馬 $p_j$ の満足度が以下で与えられます。
- $j = 1$ の場合、馬 $p_j$ の機嫌。
- $j \geq 2$ の場合、馬 $p_j$ の機嫌と馬 $p_{j-1}$ の丁寧さの積。
- これから $Q$ 日にわたって毎日 $1$ つずつクエリが与えられるので、順に処理してください。$k$ 日目 ($1 \leq k \leq Q$) のクエリは以下の通りです。
- 馬 $W_k$ の機嫌を $X_k$ に、丁寧さを $Y_k$ に変更する(ここで、$Y_k$ は $1, 2$ のいずれか)。
- その後、その夜に $N$ 頭の馬が水浴びをする順番を任意に決められるとき、その夜の $N$ 頭の水浴びの満足度の総和が最大でいくらになるかを求める。
制約
- $1 \leq N \leq 2 \times 10^5$
- $1 \leq Q \leq 2 \times 10^5$
- $1 \leq A_i \leq 10^6$ ($1 \leq i \leq N$)
- $B_i$ は $1,2$ のいずれか ($1 \leq i \leq N$)
- $1 \leq W_k \leq N$ ($1 \leq k \leq Q$)
- $1 \leq X_k \leq 10^6$ ($1 \leq k \leq Q$)
- $Y_k$ は $1,2$ のいずれか ($1 \leq k \leq Q$)
- 入力される値はすべて整数
解法
丁寧さが $2$ である馬を「丁寧な馬」、$1$ である馬を「丁寧でない馬」とする。
基本的に、機嫌がいい馬を丁寧な馬の次に持ってくるのがスコア(満足度の総和)が高くなる。
なんとなく、機嫌と丁寧さが決まっているとき、スコアの最大値は以下のように求められそうである。
- なんとなく解法(WA)
- 丁寧な馬の頭数を $k$ とする
- その時点で機嫌のいい馬の上位 $k$ 頭の機嫌 $A_i$ の和を $S$ とする。
- それ以外の機嫌 $A_i$ の和を $T$ とする。
- 機嫌のいい $k$ 頭を丁寧な馬の次に持ってくることで、$S$ を2倍でスコアに寄与させられる。
- $2S+T$ がスコアの最大値となる。
大まかには正しいのだが、しかし、以下の点で誤りがある。
- 上位 $k$ 頭に、丁寧な馬が全て入っている場合
- 丁寧な馬は、少なくとも1頭は、丁寧でない馬の後(または先頭)に置く必要がある。
- この場合は、丁寧な馬の中で $A_i$ 最小の馬を犠牲にし、代わりに $k+1$ 番目に $A_i$ が大きい馬を $S$ に採用するのが最適
- $N$ 頭全てが丁寧な馬の場合
- どれかは先頭に置く必要があるので、2倍にできるのは $N-1$ 頭までとなる。
この2つを解消するために、各時点で以下が取得できるようなデータ構造を管理したい。
- $X_k:=$ 全ての馬の機嫌 $A_i$ のうち、上位 $k$ 個の和(任意の $k$ につき)
- $Y:=$ 丁寧な馬のみの中で、機嫌 $A_i$ の最小値
- $Z:=$ 丁寧な馬の頭数
すると、適切な解法では、以下のようにスコアを求められる。
- 正しい解法
- 丁寧な馬の頭数を $k$ とする
- ただし、$k=N$ の時、代わりに $k=N-1$ とする。
- $X$ から $Y$ を一時的に取り除く。その後、$X_k$ を取得し、$S$ とする。
- それ以外の機嫌の和を $T$ とする。($Y$ を含め)
- $2S+T$ がスコアの最大値となる。
$X$ をSortedSet、$Y,Z$ を削除可能な優先度付きキューなどで管理することで、クエリ毎に更新・取得できる。
(ただし、Pythonの場合TLEが取れなかったため、$X$ は個数と値をそれぞれ管理するFenwickTree2本で実装して通った)
G - Haunted House
問題文
- $F$ 階だてのお化け屋敷があり、各階は $H$ 行 $W$ 列のグリッドで表されます。
- $k$ 階を表すグリッドの上から $i$ 行目・左から $j$ 列目のマスをマス $(k,i,j)$ とします。
- マス $(k,i,j)$ の内容は文字 $S_{k,i,j}$ によって、以下のように表されます。
- $S_{k,i,j}$ が数字 (
0-9) の場合、マス $(k,i,j)$ は空きマスであり、コインが $S_{k,i,j}$ 枚ある。 - $S_{k,i,j}$ が
#の場合、マス $(k,i,j)$ は壁マスである。
- 空きマス $(k,i,j)$ からほかのマス $(k', i', j')$ に直接移動できる条件は、マス $(k', i', j')$ が空きマスであり、かつ以下のいずれかを満たすことです。
- 同じ階層で隣接する部屋($k' = k$ かつ $|i' - i| + |j' - j| = 1$)
- ちょうど真上の部屋($(k', i', j') = (k+1, i, j)$)
- ちょうど真下の部屋($(k', i', j') = (k-1, i, j)$)で、かつマス $(k,i,j)$ にハシゴが置かれている。
- グリッドの外部に移動することはできません。
- $Q$ 個の質問が与えられるので、それぞれの答えを求めてください。$i$ 個目の質問 ($1 \leq i \leq Q$) は以下の通りです。
- 好きな空きマスを $1$ つだけ選んでハシゴを置いてから、マス $(G_i, A_i, B_i)$ を開始地点として移動を繰り返したとき、訪れたマスにあるコインの合計枚数の最大値を求めよ。
- なお、各質問は独立です。つまり、ある質問で置いたハシゴは、ほかの質問に影響を与えません。
制約
- $F,H,W$ は整数
- $1 \leq F \leq 10$
- $1 \leq H,W \leq 500$
- $S_{k,i,j}$ は数字 (
0,1,2,3,4,5,6,7,8,9) または#のいずれか ($1 \leq k \leq F, 1 \leq i \leq H, 1 \leq j \leq W$) - $Q$ は整数
- $1 \leq Q \leq 10^5$
- $G_i, A_i, B_i$ は整数 ($1 \leq i \leq Q$)
- $1 \leq G_i \leq F$ ($1 \leq i \leq Q$)
- $1 \leq A_i \leq H$ ($1 \leq i \leq Q$)
- $1 \leq B_i \leq W$ ($1 \leq i \leq Q$)
- $S_{G_i, A_i, B_i}$ は
#でない ($1 \leq i \leq Q$)
解法
解法は難しいわけではないが、実装が面倒&細かな場合分けを見落としやすい。
E,F も軽くはない問題セットで、最後にこれはなかなかきつい。
問題文とは異なるが、図で考察する際に個人的なイメージのしやすさから、階の順序を逆とする。
つまり「1階が一番上で、F階が一番下の階」とし、降りるときにハシゴは不要で、上るときにハシゴが必要とする。
「同じ階層で隣接し、互いに行き来できる部屋群」を、1つの「頂点」とする。
Union-Find などで連結し、その部屋群にあるコインを全て合計して、その「頂点にあるコイン」としておく。
また、上下で隣接する空きマスがそれぞれ所属する頂点を、上階:$u$、下階:$v$ として、$u→v$ に辺を張る。
すると、DAGができる。
サンプル1
1F ⓪8 ①4 ②2
↘ ↙↘ ↙ 丸数字は頂点番号、
2F ③8 ④12 その横は各頂点にあるコイン
↘
3F ⑤0 ⑥4 ⑦18
各頂点に付き、このDAGを「逆辺を1回まで通ることを許容して」辿り着ける頂点で拾える コインの合計の最大値を前計算しておけば、クエリにすぐ答えられる。
だが、いきなり「$\mathrm{DP}_仮[i]:=i$ から逆辺を高々1回使って拾えるコインの最大値」を求めようとしても、更新が難しい。 段階を踏んで求めていく。
- $\mathrm{DP}_0[i]:=i$ から逆辺を使わずに拾えるコインの(最大値, 最大値を与える遷移先の1つ, 2番目の値)
- $\mathrm{DP}_1[i]:=i$ で逆辺を使い、その後逆辺を使わずに拾えるコインの(最大値, 最大値を与える遷移先の1つ, 2番目の値)
- $\mathrm{DP}_2[i]:=i$ では逆辺を使わず、その後高々1回使うことで拾えるコインの最大値
$\mathrm{DP}_0,\mathrm{DP}_1$ では2番目の値まで求めているが、これが必要な理由は後述する。遷移先が存在せず、最大値や2番目の値が無い場合は、$-\infty$ とでもしておく。
以下の記号を定義する。
- $c_v$: 頂点 $v$ にあるコイン
- $E_v$: 頂点 $v$ から1ステップで遷移できる頂点集合
- $R_v$: 頂点 $v$ へ1ステップで遷移できる頂点集合(逆辺)
$\mathrm{DP}_0$ は、下から確定していける。
- $\mathrm{DP}_0[u]$ を求めるとき
- $u→v$ に遷移したときのスコア: $c_u + \mathrm{DP}_0[v]$
- $v \in E_u$ を全て調べて上位2つを記録
$\mathrm{DP}_1$ は、$\mathrm{DP}_0$ が最大値しか持っていなかった場合、困ったことになる。
- $\mathrm{DP}_1[u]$ を求めるとき(仮に $\mathrm{DP}_0[v]$ が最大値しか持っていなかった場合)
- $u→v$($v \in R_u$)に遷移したときのスコア?: $c_u + \mathrm{DP}_0[v]$
上例で、例えば $u=3$ なら、③→①→④→⑦ が最適なのでこれで正しく求まる。
一方、$u=4$ の時、④→①→④→⑦ が最適だが、④を2回通っている。このような時、④のコインは1回しか数えてはいけない。
しかし、前述のDPの更新では、$c_4 + \mathrm{DP}_0[1]$ が計算され、$\mathrm{DP}_0[1]$ の実体は $c_1+c_4+c_7$ なので、$c_4$ が2回数えられてしまう。
これを避けるために、$\mathrm{DP}_0$ では2番目の値まで持っておく。 すると、$\mathrm{DP}_1[u]$ は、以下の $v \in R_u$ に渡っての上位2つとなる。
- $u \neq trans_{0}(v)$ なら、$c_u + best_{0}(v)$
- $u = trans_{0}(v)$ なら、$\max(best_{0}(v), c_u+second_{0}(v))$
- ただし、
- $best_{0}(v)$: $\mathrm{DP}_0[v]$ の最大値
- $trans_{0}(v)$: $\mathrm{DP}_0[v]$ の最大値を与える遷移先
- $second_{0}(v)$: $\mathrm{DP}_0[v]$ の2番目の値
$\mathrm{DP}_2$ を求める際も同様の問題が発生するため、$\mathrm{DP}_1$ も2番目の値まで計算しておく。($best,trans,second$ も同様に定義)
その上で、$\mathrm{DP}_2[u]$ は以下の $v \in E_u$ に渡っての最大値となる。$\mathrm{DP}_0$ と同様、下から埋めていく。
- $u \neq trans_{1}(v)$ なら、$\max(c_u + best_{1}(v), c_u + \mathrm{DP}_2[v])$
- $u = trans_{1}(v)$ なら、$\max(best_{1}(v), c_u+second_{1}(v), c_u+\mathrm{DP}_2[v])$
以上で計算しておくと、クエリはスタートの頂点を $v$ として $\max(best_{1}(v),\mathrm{DP}_2[v])$ で答えられる。

