目次

日本レジストリサービス(JPRS)プログラミングコンテスト2024#2(AtCoder Beginner Contest 364)F,G問題メモ

日本レジストリサービス(JPRS)プログラミングコンテスト2024#2(AtCoder Beginner Contest 364)

F - Range Connect MST

F - Range Connect MST

問題文

制約

解法

辺が多すぎるのでTLEになるが、まずはクラスカル法でやるとどういう流れになるか把握する。

$C_i$ が小さい順に辺を取り出し、両端が連結じゃなければその辺を採用する。
同じ $i$ から出る辺は同コストなのでまとめて考えるとして、 はじめのうちは、$L_i~R_i$ の全てに $i$ からの辺が張られていく。

 i      0     1     2     3     4
Ci      9     6     1     4     2
Q側    ○    ○    ○    ○    ○    5本の辺が採用され、
                //|\\             5 x 1 がコストに計上される
N側    ○○○○○○○○○○○○○
               L2------R2

 i      0     1     2     3     4
Ci      9     6     1     4     2
Q側    ○    ○    ○    ○    ○    3本の辺が採用され、
                //|\\    //|    3 x 2 がコストに計上される
N側    ○○○○○○○○○○○○○
                           L4--R4

$i=3$ を処理する時、一部は既に他の $i$ により連結になっている。$i=3$ からは連結なうちの1つに対してのみ結びにいけばよい。

 i      0     1     2     3     4
Ci      9     6     1     4     2
Q側    ○    ○    ○    ○    ○
                //|\\    //|
N側    ○○○○○○○○○○○○○
                 L3----------R3
                 |------|          ←i=3からは、この4つにはどれか1つにのみ結びにいけば十分
                           |--|    ←この2つにも同様、どれか1つにのみ結びにいけば十分

性質上、$N$ 側頂点の連結は「$j=3~11$ の範囲が連結」みたいに連続した範囲となり、 「$j=3,4,8,11$ が連結」のように飛び飛びになることはない。
この「どこからどこまでが結ばれているか」の情報を上手く管理したい。

飛ばせるindexを管理

$N$ 側頂点に対し「自分以降で、少なくともここまでは同一連結成分」という情報を持つ配列 jump を定義する。

初期状態は $jump = [0,1,2,...,N-1]$ である。

$Q$ 側頂点 $i$ を処理する際、$N$ 側頂点を $j=L_i$ から順に見ていく。
$jump[j]=j$ ならそこで連結が途切れているので、そこまでの連結成分と $i$ を結ぶため $C_i$ をコストに加算し、$j+=1$ する。
それ以外の場合、$j←jump[j]$ とする。
$j \ge R_i$ になるまで辿ったら、最後の連結成分と結ぶため、$C_i$ を追加でコストに加算すればOK。

上記の過程で経由した $j$ は、全て $jump[j]←\max(jump[j],jump[Ri])$ に更新していく。

 i      0     1     2     3     4
Ci      9     6     1     4     2
Q側    ○    ○    ○    ○    ○
                //|\\    //|
N側    ○○○○○○○○○○○○○
jump    0 1 2 3 4 5 6 7 8 9101112
                v v v v     v v
jump    0 1 2 3 8 8 8 8 8 9121212

こうすると、$i=3$ を処理する時、$L_i~R_i$ を全てチェックしなくてもよくなる。

 i      0     1     2     3     4
Ci      9     6     1     4     2
Q側    ○    ○    ○    ○    ○
                //|\\    //|
N側    ○○○○○○○○○○○○○
jump    0 1 2 3 8 8 8 8 8 9121212
                 L3          R3
                  .     * * .      チェックされる頂点(*はコスト加算される箇所)

あるクエリで $k$ 個の頂点をチェックしたら、次以降、$k$ 個の中で1クエリ中に再度チェックされる頂点は高々2個までになる。
全体で、$O(N+Q)$ で求められる。

最後に、$j=0$ からjumpを辿って $N-1$ に辿り着けるか判定する。(辿り着けなければ連結にできない)

Python3

解法2

連結性を考えたとき、辺が下記のように張られるものと捉えなおしても問題ない。

Q側    ○       ○     _ ○ _     ○     _ ○
                     / /|\ \        / /|    元の問題の辺の張り方
N側    ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○ ○

                         ↓

Q側    ○       ○     _ ○       ○     _ ○
                     /                /        捉えなおした辺の張り方
N側    ○ ○ ○ ○ ○-○-○-○-○ ○ ○-○-○

すると、$i→L_i$ の辺は必ず必要なので $\sum_i{C_i}$ は別計上するとして、 $N$ 側を横に結ぶ辺は、その中で最小コストの辺を使えば良いことになる。

遅延評価セグメント木を使うこともできるが、イベントソートでも解ける。

イベントソートすることで、「隣接2頂点間に張られている辺コスト $C_i$ の集合が同じである区間」をまとめて処理できる。
要素削除可能なヒープキューで $C_i$ の集合を管理することで、各区間の最小 $C_i$ を取得できる。

途中でヒープキューが空になったらアウト。

計算量は $O(Q\log{Q})$ となり、大きい $N$ にも耐えられる。

G - Last Major City

G - Last Major City

問題文

制約

解法

最小シュタイナー木。
わりと知識問題であり、アルゴリズムを知っていたらその過程で得られる情報がそのまま答えとなるのに気付くのは容易い。

最小シュタイナー木問題は、ぱっと見、それっぽい近似値は出せそうではあるが、厳密解はNP困難である。
厳密解を求める方法として Dreyfus–Wagner法が知られる。

連結にしたい頂点(問題中の“主要都市”)を「ターミナル」と呼ぶ。

$S$ が小さい方から求めていけば、最終的な結果から各 $v=K,K+1,...,N$ に対し $DP[\{1,2,...,K-1\}][v]$ が答えとなる。

遷移について、$DP[S][v]$ は以下の最小値となる。

①は、部分集合を全探索するbitDPによくあるやつで、 全体を通して $3^K$ かかり、それが各 $v$ に対し発生するので $O(N 3^K)$

②は、①で更新した後、全頂点を始点としたDijkstraでコストが小さい方から更新していく。
計算量は$O(2^K (N+M)\log{N})$ となる。

Python3