Loading [MathJax]/jax/output/CommonHTML/jax.js

目次

トヨタ自動車プログラミングコンテスト2023#1(AtCoder Beginner Contest 298) G,Ex問題メモ

トヨタ自動車プログラミングコンテスト2023#1(AtCoder Beginner Contest 298)

Ratedの人はたまったもんじゃなかろうね。

G - Strawberry War

G - Strawberry War

問題

解法

H,W の制約がめちゃ小さいが、ケーキの切り方も自由度が高い。

こういうのを解くには、2次元区間DP(一般的な呼び方は知らない)が適する。

自由に切れるとはいえ、途中までナイフを入れて止める、みたいな切り方はできず、 1回の分割では必ず縦か横にナイフを通しきる必要がある。

こういうのはダメ    こういうのはいい
┌─┬┐            ┌─┬┐
├┬┤│            ├┬┤│
│├┴┤            │├┤│
└┴─┘            └┴┴┘

よって、[u,d)[l,r) 列の部分長方形は、 「全く切らない」か「2つの矩形に分割」のいずれかに限られる。

2つの長方形に分割する場合の切り口の候補は、その長方形の (高さ-1)+(幅-1) 個。

区間DP的に、小さい長方形から求めていく。

とりあえず、ちょうど T 回切らないといけないので回数の情報も必要で、以下の添字を持ったDPが考えられる。

だが、この(いい感じに答えが求められる何か)の内容が意外と難しい。
2つの長方形を1つに統合した結果を、ある程度簡単に求められないといけない。

「最大と最小の差の最小化」で他によく使われる考え方として、「最小値の固定」がある。
最小値を固定すれば、その前提の中で最大値を最小化すればよくなる。

固定するべき最小値をどうやって決めればいいのか迷うが、 実は 6×6 の矩形の部分長方形は u,d,l,r の決め方 441 通りと意外と少なく、 最小値はこの上に載っているイチゴの数のどれかになるのは明らか。
これを全通り試せばよい。

最小値を m に固定したら、先ほどと同様、全部分長方形を u,d,l,r の全探索で調べて、 切らない場合 DP[,,,,0] を埋められる。
この時、m 未満になる場合は最小値の前提に反するので INF として、不可能であることを示しておく。

あとは、小さい矩形から順番に求めていき、DP[0,H,0,W,T]m が最小値を m にした時の答え。

計算量は、最小値の固定の仕方が O(H2W2)、DPの状態数が O(H2W2T)、 遷移が「どこで切るか」と「2つの矩形の切った回数を何回と何回にするか」で O((H+W)T)

N=H=W としてざっくり O(N13) となるが、 まぁ、実際は lr の大小関係が決まってたり、 l,r が小さい内は遷移の T の部分も少ないので省略できるなど、 簡単にできる枝刈りをすれば十分通る。

Python3

解法2

2次元区間DPは同じだが、「最大値」と「最小値」を両方固定する場合も考えられる。その上で、

をbitsetで持っておく等でも解けるみたい。

計算量は多少増えるため、最大や最小の時点で明らかに不可能なものはスキップするなどの枝刈りが必要となる。

Ex - Sum of Min of Length

Ex - Sum of Min of Length

問題

解法

R の方が近い頂点」「L の方が近い頂点」は、LR の真ん中の点または辺が境界となってはっきり分かれる。

●-❶-,   ↓   ,-○       (フォントの都合上、Lを❶、Rを②と表現)
   ●-●--◎--○-②-○
●-●-'   /       `-○
     ◎-◎

真ん中が点の場合、その点、およびその点から出てる他の枝(◎)はどっちでも一緒なので、どっちかに属させればよい。
(ここでは以降、●に属させる)

で、❶から各●への距離の和と、②から各○への距離の和を別々に求めて合わせれば答えとなる。

❶か②を根としたとき、自分に属さない方の頂点は、中央(付近)の頂点を根とする部分木の形になる。

  ❶
  /\
 ●●
  /|\
 ●●❹←中央
   ||\
   ●● ③    ③以下の部分木は②の方に属する
     | /\
     ●○②
         /\
        ○○

なので、

が高速に求められれば、答えの半分を出せる。②も同様に求めて合わせれば答え(その場合、引くべき部分木の根は❹)。

❶を根として、各頂点 v の以下の情報が計算されているとする。

すると、以下が成り立つ。

❶から他の全頂点への距離は ds1,1 なので、これで答えの半分が出せた。

今は❶を根としたが、ds,cnt はクエリの各 L,R を根とした場合について必要で、これを個別に求めていれば間に合わない。

だが、実際は、根を1つ隣の頂点に移したとき、変わる部分というのは限られる。(更新の仕方は省略)

  ❶
  /\
 ❺ ●
 /\ :
●●
::

なので、まず根❶について求めた後、オイラーツアーの順番で根を移動させながら ds,cnt の一部だけを更新する、 ということを繰り返せば、全頂点を根としたときの ds,cnt が求められる。

du,v は、根を更新すればごそっと1増えたり減ったりで変わってしまうのでこの更新には向かないが、 これは各クエリにつき中央の点を求めるときに一緒に求めておけばよい。

まとめ

計算量は、何回も木を走査するので定数倍はそれなりにかかるが、 ダブリングの部分で O(NlogN)、木DPで O(N)、オイラーツアーで O(N) となる。

Python3