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

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

G - Strawberry War

問題

  • 長方形のケーキがある。ケーキは HW 列に並ぶ区画に分かれている
  • 上から i 行目、左から j 列目の区画にはイチゴが si,j 個載っている。
  • T 回の切断を行ってケーキを T+1 切れに分割する。各回の切断は、次のいずれかの方法で行う。
    • 現存するケーキであって、区画の行の数が 2 以上であるものを選ぶ。さらに、そのケーキから隣接する 2 行を選び、その境界でケーキを切断してより小さなケーキ 2 切れに分割する。
    • 現存するケーキであって、区画の列の数が 2 以上であるものを選ぶ。さらに、そのケーキから隣接する 2 列を選び、その境界でケーキを切断してより小さなケーキ 2 切れに分割する。
  • 分割後のケーキに載ったイチゴの数をできるだけ均等にしたい。
  • 分割後の T+1 切れのケーキに載ったイチゴの個数の最大 M と最小 m の差 Mm がとりうる最小値を求めよ。
  • 1H,W6
  • 0si,j1016

解法

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

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

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

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

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

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

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

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

  • DP[u,d,l,r,c]=[u,d)[l,r) 列の部分長方形を c 回切った時に(いい感じに答えが求められる何か)

だが、この(いい感じに答えが求められる何か)の内容が意外と難しい。
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は同じだが、「最大値」と「最小値」を両方固定する場合も考えられる。その上で、

  • DP[u,d,l,r]=[u,d)[l,r) 列の長方形を、全ての区画を最小~最大の範囲に収めつつ、何回切断できるかの組

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

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

Ex - Sum of Min of Length

問題

  • N 頂点の木が与えられる
  • 頂点 u,v 間の距離(パスの辺数)を d(u,v) で表すとする
  • Q 個のクエリに答えよ
    • L,R が与えられるので、Nv=1min(d(v,L),d(v,R)) を出力する
    • つまり、全ての頂点についての、LR 近い方との距離の総和
  • 1N,Q2×105

解法

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

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

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

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

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

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

なので、

  • (❶から他の全頂点への距離の総和)-(❶から③を根とする部分木の各頂点への距離の総和)

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

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

  • 自身から、自身を根とする部分木の各頂点との距離の総和 ds1,v
  • 自身を根とする部分木の頂点数 cnt1,v
  • 根から自身までの距離 d1,v

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

  • (❶から③を根とする部分木との距離の総和)=(③から③を根とする部分木との距離の総和 ds1,3)+(③を根とする部分木の頂点数 cnt1,3)×(❶と③の距離 d1,3

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

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

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

  ❶
  /\
 ❺ ●
 /\ :
●●
::
  • ds5,v は、ds5,1ds5,5 を除き、ds1,v と変わらない
  • cnt5,v も、cnt5,1cnt5,5 を除き、cnt1,v と変わらない

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

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

まとめ

  • 木からダブリングを構築し、LCAや“k 遡った頂点” を高速に求められるようにしておく
  • クエリ L,R につき、以下の事前加工をおこなう
    • LCAを求め、距離を求め、中央の点(L,R それぞれを根としたときに除く部分木の根)を求める
    • 各頂点に付き、(何番目のクエリで必要か, 除く部分木の根 w, 自身と w との距離)を登録する
  • 1を根として ds1,v,cnt1,v を求める
  • オイラーツアー順に根を移動させながら、クエリで必要な頂点が根になったタイミングで、事前加工で登録した情報を元に、答えの半分を計算する

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

Python3

programming_algorithm/contest_history/atcoder/2023/0415_abc298.txt · 最終更新: 2023/04/23 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0