Processing math: 100%

ZONeエナジー プログラミングコンテスト “HELLO SPACE” C,E,F問題メモ

ZONeエナジー プログラミングコンテスト “HELLO SPACE”

人はエナジードリンクを飲むのがきつくなってきたときに老いを実感する。

C - MAD TEAM

問題

  • N 人の人がいて、各人、パワー・スピード・テクニック・知識・発想力の5種類の能力値を持っている
  • この中から3人を選んでチームを作る
  • チームの「総合力」は以下のように決定する
    • チームの各能力値は、3人の最大値をとる
    • チームの総合力は、チームの能力値の最小値である
  • 上手く3人を選んで、達成できるチームの総合力の最大値を求めよ
  • 3N3000

          A  B  C  D  E
    人1   1  2  3  4  5
    人2   9  7  5  3  1
    人3   3  1  4  1  5
MAX -------------------
    TEAM  9  7  5  4  5   --MIN→   総合力 4

解法

二分探索+bitDP。

「チームの総合力をある値 m 以上にできるか?」にそれなりに高速に答えられたら二分探索が可能なので、その方向で考えてみる。

例を考えてみると、「3人で、5個の能力全てについて、誰かが m 以上であればOK」であることがわかる。

      A  B  C  D  E
人1   1  2  3  4  5
人2   9  7  5  3  1
人3   3  1  4  1  5

人1   x  x  x  o  o
人2   o  o  o  x  x    4以上にできる?
人3   x  x  o  x  o
-------------------
      o  o  o  o  o    できる!

人1   x  x  x  x  o
人2   o  o  o  x  x    5以上にできる?
人3   x  x  x  x  o
-------------------
      o  o  o  x  o    ダメ~

すると、各能力値を超えている人が1人でもいればいい、というのは、bit集合のORで表現できそう。

以下のDPを考えると、

  • DP[i][j][k]=i 番目の人までのうち j 人を採用することで、m 以上の能力の集合が k となるものを作れる(True/False)

i=13000,j=03,k=031(bitフラグ)より、全体で 3.8×105 程度の計算量で達成可能かどうか求められる。

二分探索で 109 個の値を探索しても、3.8×105×log109=1.15×107 程度の計算量で答えを求められる。

Python3

m 以上かどうか? の判定では1人の能力パターンはたかだか 32 通りしかないので、同じ人は圧縮して、32C3 を全て試す、というのでもよかった。

発展

  • 総合力のスコアが、チームの5つの能力値の「min」でなく「sum」ならどうするか?

二分探索が使えなくなる。

「5」つの能力値から「3」人を選ぶ、という設定を利用することで、O(N2) で解くことができる。

3人を選んだとき、必ず1人は「自身の能力値がチーム能力値に採用される(3人の中で最大である)能力が5個中1個以下」の人が存在する。 (同率の場合は採用されていないものと考える)

そうでないならば、3人がそれぞれ別々の「自身の能力値がチーム能力値に採用されている能力」を2つずつ持つことになり、能力が6つ必要となるので矛盾する。

なので、

  • 各能力について、最大の人の能力値を求めておく
  • 3人中2人を固定して暫定のチーム能力値を計算した後、どれか1つを最大の人の能力値と置き換える
  • その中で最大のものが答え

となる。

E - Sneaking

問題

  • グリッドを左上 (1,1) から右下 (R,C) まで移動する
  • 移動の条件
    • (1,1)(R,C) の長方形の外にはいけない
    • コスト
      • コストを表す値 Ai,jBi,j が与えられる
      • (i,j) から1つ右に移動するコストは、Ai,j
      • (i,j) から1つ左に移動するコストは、Ai,j1
      • (i,j) から1つ下に移動するコストは、Bi,j
      • (i,j) から k マス上に移動するコストは、1+k
  • 最小コストを求めよ
  • 2R,C500

解法

その通り実装する……のはE問題にしてはやけに率直だなと思ったが、想定解はもう少し工夫するものだったらしい。

愚直解

愚直解だと、各マスからの遷移がたかだか R+2 なので全部で O(R2C)、頂点数が O(RC) で、Dijkstraの計算量に当てはめると O(R2ClogRC) となる。 最大値で 2.25×109 なので枝刈りを丁寧にやらないとTLEとなるが、まぁ間に合うことにワンチャン賭けてもいいくらいのオーダーではある。

Dijkstraの実装、本来は

  • 始点から各頂点までの暫定の最小コストを管理する
  • 遷移時、現頂点から遷移した場合のコストと、遷移先の暫定コストを比較して、最小を更新できないならキューに突っ込まない

という手段でかなりの枝刈りが見込めるが、下手な実装で

  • とりあえず遷移先全部heapqに放り込んどいて、popした時に初めて訪れた頂点なら次に遷移する

としてしまうと、キューが肥大化してしまうので遅くなる。ちゃんと前者で実装すれば、PyPyでも通る。

通らない場合は、以下の(小手先の)工夫の余地がある。

  • なんとなく (i,j) がゴールに近い方がよさそうなので、コストが同じ場合そちらを優先的に探索されるようにする
    • キューに入れるアイテムにゴールまでのマンハッタン距離を入れるなど
  • (cost,i,j) をタプルで持つより、bitシフトして1つの整数値として表現する

Python3

よりよい解法

4方向の移動のうち上に移動する場合が、最大 R1 マスに遷移することになり、辺の数がかさみやすい部分である。

ただ、マス (i,j) から上方向へのコストはかなり統一的で、「1マス上が2、2マス上が3、3マス上が4、……」である。

もし、これが「1マス上が1、2マス上が2、3マス上が3、……」だったら、嬉しいことがある。

各マスから1つ上のマスにコスト1の辺を張っておけば、(i,j) からは1つ上のマスへの遷移だけで済む。
2マス以上上のマスへの遷移は、1つ上のマスへの遷移を繰り返すことで表現できる。

     □<---,      □
           |3     ↑1
     □<,  |      □
        |2 |  =  ↑1
     □ |  |      □
    1↑ |  |      ↑1
(i,j)■-'--'      ■

よって辺の数が O(R2C) から O(RC) へと縮小し、計算量が O(RClogRC) へと減る。
(実際は、2倍の頂点を持つことなどによりそこまで大幅に改善するわけではない)

元の問題の場合も、マスを2つ持つ(2つの世界線を行き来する)ことで似たことを実現できる。

  • 現世界線のマスからは、
    • 現世界線の1つ左、右、下マスへ移動できる(コストは問題文通り)
    • 別世界線への同じマスへ移動できる(コストは1)
  • 別世界線のマスからは、
    • 別世界線の1つ上のマスへ移動できる(コストは1)
    • 現世界線の同じマスへ移動できる(コストは0)

こうすると、問題文通りのコストを実現しつつ、遷移の数が減らせる。

Python3

F - Encounter and Farewell

問題

  • N 個の星を N1 個のワープゲートで連結にする
    • N は2べき(n を整数として N=2nと表現できる)
  • 星は 0N1 の番号が付いている
  • ただし、直接は繋げられない星の制約が M 個ある
    • A1,A2,...,AM が与えられる
    • abAi のどれかになるような星 a,b 間にはワープゲートを構築できない( はbitXORの記号とする)
  • 制約下でワープゲートを構築する方法があるか判定し、可能なら構築方法を1つ示せ
  • 2N218
  • 0M<N

解法

グラフ問題。星を頂点、ワープゲートを辺と見立てて、制約がある中で全域木を作れという問題。

制約を逆に考えると、XORが Ai に含まれないような2頂点間には辺を張れる。

よって問題は、「あるグラフが与えられるので、連結か判定し、連結なら全域木を1つ答えよ」と言い換えられる。
グラフの辺はXORが Ai に含まれないような2頂点間全てに張られているものとする。

全域木の一般的な作り方は、

  • Union-Findで連結状態を管理する。はじめは全頂点バラバラとする
  • 辺を任意の順番で見ていって、その辺が結ぶ2頂点が未連結ならその辺を使うこととし、Union-Find上でも結合する

としていくと、N1 本の辺が選ばれたらそれが全域木の辺となっている。

しかし、今回の問題では Ai の個数が少ない場合、元のグラフの辺は O(N2) くらいのオーダーになる。これでは間に合わない。

ここで、辺が張られる条件に着目すると、

  • ある X について、ab=X となる a,b 全てに辺を張る
  • ある Y について、bc=Y となる b,c 全てに辺を張る

この2つの操作を行った後では、既に ac=XY となる a,c 間は b を介して連結状態になっている。

従って、

  • 「既に連結状態となっているXORの集合 C」を {0} で初期化する
  • 12N1 のうち Ai に含まれないような X を任意の順に試す
    • もし既に XC に含まれていたら、ab=X となる全ての (a,b) は既に連結状態なので飛ばす
    • まだ XC に含まれていなければ、
      • ab=X となる辺 (a,b) を全域木に加えられるか全て試す
        • この操作の最初の時点ではどの (a,b) も連結では無いはずだが、先に処理された同じ X を持つ辺によって他が連結になるかも知れないので、順番に試す
      • C に、(C)X を加える

とするとよい。

実際に XC に含まれていない場合というのは O(logN) に絞られる。

その中で全域木チェックが O(N)(Union-Findの結合はほぼ定数として)、C を拡張する操作は全体を合計して O(N) となり、O(NlogN) で解ける。

Python3

programming_algorithm/contest_history/atcoder/2021/0501_zone2021.txt · 最終更新: 2021/05/06 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0