M - NUPaCking 解説 by physics0523

LPソルバを利用した解法

連結成分の分類方法は 公式解説 と同じです。

また、大前提として AtCoder の Python では PuLP を使って数理最適ソルバを利用することができることがあります。 参考1 参考2 参考3 参考4
これはオンラインジャッジではかなり珍しい仕様であり、他のコンテストではほとんど使用出来ないことに留意してください。
では、始めましょう。

制約より、単一の連結成分に含まれる頂点の数は \(10\) 以下なので、単一の連結成分で行えることは次の \(7\) つです。

  • \(N\)\(1\) つ詰め込む。
  • \(U\)\(1\) つ詰め込む。
  • \(P\)\(1\) つ詰め込む。
  • \(C\)\(1\) つ詰め込む。
  • \(P\)\(2\) つ詰め込む。
  • \(P,C\)\(1\) つずつ詰め込む。
  • \(C\)\(2\) つ詰め込む。

この \(7\) 通りのうち、どれが可能でどれが不可能かをバイナリ配列に書き起こすことにします。
例えば、長さ \(10\) のパスグラフでは \((1,1,1,1,0,0,1)\) となります。

幸いにも、このバイナリ配列の種類数はそう多くないことが分かります ( ただでさえ \(2^7=128\) 通り以下と評価できるほか、公式解説の図の包含関係を見ると \((0,0,0,0,0,0,0)\) を含めて \(12\) 通りまで減ることが分かります )。

ここで、本問題を上記の観察を使って整数計画問題に書き起こすことを考えます。

\[ \begin{aligned} &\text{maximize} & & K \\ &\text{subject to} & & s_{\rm N} \ge K \\ & & & s_{\rm U} \ge K \\ & & & s_{\rm P} \ge K \\ & & & s_{\rm C} \ge K \\ & & & s_{\rm N} = c_0 \\ & & & s_{\rm U} = c_1 \\ & & & s_{\rm P} = c_2 + 2 c_4 + c_5 \\ & & & s_{\rm C} = c_3 + c_5 + 2 c_6 \\ & & & K, s_i, c_i \in \mathbb{Z}_{\ge 0} \end{aligned} \]

ここで、 \(c_i\) は最初の \(7\) 通りの詰め込みのうち \(i\) 通り目を行った連結成分の数とします。

ここに、各連結成分の情報を追加していきましょう。
先程のバイナリ配列で \((1,1,1,1,0,0,1)\) と表現される連結成分が \(t\) 個存在するとき、それらの中で \(d_i\)\(7\) 通りの詰め込みのうち \(i\) 通り目を行った連結成分の数とすると、以下の制約式が追加されることが分かります。

\[ \begin{aligned} & d_0 + d_1 + d_2 + d_3 + d_6 \le t \\ & d_4 = 0 \\ & d_5 = 0 \\ & d_i \in \mathbb{Z}_{\ge 0} \end{aligned} \]

あとは、バイナリ配列の種類ごとにこの作業を行えばよいです。
先程の考察から変数の総数が \(100\) 個程度のオーダーで済むことが分かるので、これは LP ソルバが実行時間制限内で動作するのに十分小規模であると予想できます。
実際、これを PuLP に投げることで実行時間制限に間に合います。

この方法の利点として、考察が大変そうな最適化の形が十分単純であることを見抜くことができれば、その考察をスキップすることができるというものが挙げられます。

実装例 (Python)

投稿日時:
最終更新: