Editorial for JOI '05 Final Round P5 - Sheets


Remember to use this editorial only when stuck, and not to copy-paste code from it. Please be respectful to the problem author and editorialist.
Submitting an official solution before solving the problem yourself is a bannable offence.

この問題に対する単純なアルゴリズムは,平面が頂点の座標が整数であるような 1 辺の長さが 1 の正方形のタイルからなっていると考えるやり方である.長方形を1つ入力するごとに,その長方形に含まれる各タイルの位置に印を付け,すべての長方形に対してそのような処理を終えてから,印の付いている正方形の個数を数えれば求める面積が得られる.さらに,こうして印が付けられたタイルそれぞれについて,その上下左右に隣接している 4 つのタイルに印が付いているか否かを調べることにより,出来上がった図形の周囲の長さも求めることができる.なぜなら,印が付いていないタイルに隣接する辺は,出来あがった図形の周上にあるので,そのような辺の本数を数えれば周の長さを知ることができるからである.しかし,この方法では多くの入力データでタイムオーバーになってしまう(そのように入力データを作成した).

そこで,出来上がる図形 A を与えられたタイルを何枚か縦に張り合わせた x 軸方向の長さが 1 の縦長の長方形の集まりと考えてこの問題を解く方法を考えてみよう.言い換えると,図形を間隔が 1 の y 軸に平行な直線で切り分けて考える.以下,左下の座標が (a_x, a_y) で右上の座標が (b_x, b_y) である長方形を (a_x, a_y) \times (b_x, b_y) と表し,図形 A に対して, 直線 x = x_0x = x_0 + 1 に挟まれた部分を A[x_0] と書くことにする.一般には,A[x_0]y 軸方向の長さが 1 の長方形ではなく,それらいくつかの和になっていることに注意しよう.

i 番目に入力された長方形を S_i = (x_{i1}, y_{i1}) \times (x_{i2}, y_{i2}) とする(i = 1, \dots, N).A = S_1 \cup S_2 \cup \dots \cup S_N について(\cup は和を表す)

\displaystyle A[x_0] = S_1[x_0] \cup S_2[x_0] \cup \dots \cup S_N[x_0]

が成り立つ.x_{i1} \le x_0 < x_{i2} のとき S_i[x_0] = (x_0, y_{i1}) \times (x_0 + 1, y_{i2}) であり,それ以外のとき空である.A_i = S_1 \cup S_2 \cup \dots \cup S_i に対して A_i[x_0]

\displaystyle A_i[x_0] = (x_0, a_1) \times (x_0 + 1, b_1) \cup (x_0, a_2) \times (x_0 + 1, b_2) \cup \dots \cup (x_0, a_m) \times (x_0 + 1, b_m), \displaystyle a_1 < b_1 < a_2 < b_2 < \dots < a_m < b_m

と表されていれば,A_{i+1}[x_0] を求めるのは多少ややこしいが,それほど難しくはない.例えば, a_s < x_{(i+1)1} < b_s かつ b_t < x_{(i+1)2} < a_{t+1} ならば

\displaystyle \begin{align}
A_{i+1}[x_0] &= \{(x_0, a_1) \times (x_0 + 1, b_1) \cup (x_0, a_2) \times (x_0 + 1, b_2) \cup \dots \cup (x_0, a_{s-1}) \times (x_0 + 1, b_{s-1})\} \\
&{\qquad}\cup (x_0, a_s) \times (x_0 + 1, x_{(i+2)2}) \\
&{\qquad}\cup \{(x_0, a_{t+1}) \times (x_0 + 1, b_{t+1}) \cup (x_0, a_{t+2}) \times (x_0 + 1, b_{t+2}) \cup \dots \cup (x_0, a_m) \times (x_0 + 1, b_m)\}
\end{align}

になる.したがって,必要な全ての座標について A[x_0] を計算することは可能である.さらに,入力 S_1, \dots, S_N をあらかじめ都合の良い順序に並べなおしておけば,さらに効率良く A[x_0] を計算することが出来る.

\displaystyle A_i[x_0] = (x_0, a_1) \times (x_0 + 1, b_1) \cup (x_0, a_2) \times (x_0 + 1, b_2) \cup \dots \cup (x_0, a_m) \times (x_0 + 1, b_m)

の面積は

\displaystyle (b_1 - a_1) \cup (b_2 - a_2) \cup \dots \cup (b_m - a_m)

である.こうして,A[x_0] = A_N[x_0] から A の面積を求めることができる:

A の面積 = (A[0] の面積) \cup (A[1] の面積) \cup \dots \cup (A[maxX-1] の面積)   (maxX は長方形の x 座標の最大値)

さらに A[x_0] に含まれる A の周囲で x 軸に平行な辺の長さは 2k であり,y 軸に平行な辺は両隣り A[x_0 - 1], A[x_0 + 1] と共通の部分以外の辺であり,これも k に比例した時間で計算することができる.

さて,A[x_0] をプログラム上で実装するためには 線形リスト (linear list) を用いるとメモリ使用量に関しても実行時間に関しても効率が良い(A[0], A[1], \dots, A[maxX - 1] それぞれを線形リストで表す必要があるので,それらへのポインタを要素とする配列を使う).線形リストとは,同じタイプの要素を並べた列

\langle x_1, x_2, \dots, x_n \rangle   (各 x_i は同じ型のデータで,この線形リストの要素という)

のことであり,これらがポインタで

root \to x_1 \to x_2 \to \dots \to x_n  (root はリストの先頭を指すポインタ)

のようにリンクされているものである.したがって,x_i にアクセスするには x_1, x_2, \dots, x_i の順にポインタに沿ってたどるしかない.ただし,線形リストを使うと,必要なデータしか保持しないので,メモリが効率よく使えるだけでなく,データへの総アクセス時間が結果的に短くなることが多い.この問題でも,最初に述べた方法では時間内に答が計算できない(計算に何時間もかかる)ような入力データに対しても,後で述べた方法を線形リストを使って実装すると十分短い時間内で計算が終わるように入力データを作った.ただし,この方法でも,特殊なデータに対してはかなり実行時間がかかってしまう場合があることを付記しておく.


Comments

There are no comments at the moment.