83. 最長増加部分列(LIS)の長さを算出するアルゴリズムを丁寧に見る
数列 の増加部分列(Increasing Subsequence)は、「全ての で を満たす部分列」と定義されます。この部分列は、元の数列から非連続で取ることができ、順番を変えてはいけません。
たとえば、「」という数列 の増加部分列の1つは、「」=「」です。
増加部分列のうち、その長さが最長となるものを最長増加部分列(LIS: Longest Increasing Subsequence)といいます(以降 LIS と表記します)。競技プログラミングにおいては、LIS の長さを求める問題がよく出てきます。
たとえば、先ほどの数列 、すなわち「」の最長増加部分列の長さは になります(「」=「」 が代表例です)。
LIS の長さを求めるアルゴリズムは、元の数列の長さを として のものが知られています。今回はそれについてまとめます。
LIS の長さを求めるアルゴリズム
長さ の増加部分列の最後の数を記録する配列 を準備します。この配列ですが、記録する「増加部分列の最後の数」についてできるだけ小さい値を優先するというルールを設けます。 そのため、配列の各値は で初期化します。
数列 を先頭から順番に見て、その値で を更新していきます。このとき、 は( を除いて)狭義単調増加となるように埋めていきます。
先ほどの例で具体的にどういう操作を行うのか見ていきます。まず、「」が に入ります。
次に、 を見るのですが、 はできるだけ小さい値を優先するルールなので、 が更新されます。実際、この時点で長さ 2 の増加部分列は取れないので、状態に矛盾はありません。
次に、 を見ます。これは の現在値より大きいので に入ります。実際、ここまでで長さ 2 の増加部分列を取ることができて、その最後の数の最小値は 4 になりますね。
次に、 を見るのですが、 は狭義単調増加となるように埋めるルールです。そのため、適切な場所がないので は更新しません。
…と、この調子で表を更新していくと、最終的に以下のような状態になります。実際手を動かして確かめてみてください。
以上より、最長増加部分列の長さは 4 であることが分かりました。ちなみに、この表だけでは具体的な最長増加部分列を求められない点に注意してください。実際、表に残ったのは「」ですが、このような長さ 4 の増加部分列は作れません。 は「長さ の増加部分列を作る際、最後の数となりうる最小値」を表すことを忘れないようにしましょう。
さて、このアルゴリズムの計算量を考察します。
単純に考えると、長さ の数列を見るとき、先頭から 文字を順番に確認して、その度に数値比較が 回行われるので です。
しかし、 は狭義単調増加を満たすように更新されるルールです。すなわち、更新すべき場所は二分探索で求められます。よって、数値比較は 回行われることとなり、計算量は となります。
このアルゴリズムで LIS の長さが分かる理由のイメージ
ざっとアルゴリズムを見てきましたが、なんだかよく分からない、という人も多いと思います。そこで、なぜこのアルゴリズムで LIS の長さが分かるのか、イメージを掴むコツについて書きます。
ポイントは、やはり が最小値を優先し、狭義単調増加を保つ点にあります。DP的な考え方をするとイメージが掴みやすいです。
最小値を優先して を更新することの妥当性
の値が正しいとします。すなわち、「長さ の増加部分列を取得できて、その最後の数として を取れる」ことが正しいとします。
このとき、 を保つように、できるだけ小さな に更新しても良いことが分かります。 で終わる長さ の増加部分列の最後に を追加すると、長さ の増加部分列になるからです。
また、 を小さな値に更新するほど、より長い増加部分列を作れる可能性が高まります。なぜなら、小さな値を置いた方が後続に入れられる値の種類数が増えるからです。
よって、LIS を求めるためには最小値を優先して更新するのが良いです。
狭義単調増加を保つことの妥当性
では、 が狭義単調増加を保たないとどうなるでしょうか。仮に、()となる箇所があったとします。 の定義より、第 項が となるような長さ の増加部分列を取れて、第 項は 以上になります(第 項までを見ると長さ の増加部分列となるため)。
ところが、 であるため、先ほど考えた長さ の増加部分列は増加部分列になっていません。
よって、背理法的に は狭義単調増加を保つ必要があります。
逆に、 が狭義単調増加を保つなら、 と が有効値であるようなすべての について となる と ()を取れることが保証されます。よって、 から増加部分列を構成できます。