最長増加部分列 aoj 動的計画法

DPL_1_D 最長増加部分列

DPL_1_D 最長増加部分列
Feb. 2, 2020, 1:52 p.m.

目次

問題

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DPL_1_D

増加部分列とは、ランダムに並んだ数列の先頭から順に要素が必ず大きくなっていくように要素を選んだ数列のこと.
そのうち、一番長く要素を選ぶことが出来た数列を最長増加部分列(以下、LIS)という.

例えば、 2 5 4 6という数列であれば、

増加部分列は、

  • 2
  • 5
  • 4
  • 6
  • 2 5
  • 2 4
  • 2 6
  • 5 6
  • 4 6
  • 2 5 6
  • 2 4 6

となるので、最長は最後の2つなので長さは3.

解説

LISを格納する空のコンテナを用意する.
数列を先頭から要素xを取り出して、
1) LISが空ならLISにxを加える <-- ベースケース
2) LIS最後の要素 < xであればLISの末尾にxを加える
3) x <= LIS最後の要素であれば増加列を崩さないぎりぎり最初の要素をxに置き換える

手順3ではLISは昇順にソート済みのはずなので二分探索が適用できる.
"ぎりぎり単調増加を崩さない要素"はC++の場合lower_boundで簡単に見つけられる.

計算量

解答1

$O(NlogN)$

解答2

$O(N^2)$

解答1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#define N_MAX 100001

Int N;
vector<Int> A(N_MAX, -1);
vector<Int> L;

Int lis() {
  L.push_back(A[0]); // base case
  loop(n,1,N) { // general cases
    if (last(L) < A[n]) L.push_back(A[n]);
    else *lower_bound(span_all(L), A[n]) = A[n];
  }
  return len(L);
}

int main(void) {
  cin >> N;
  loop(i,0,N) cin >> A[i];
  cout << lis() << endl;
}

解答2

こちらのアルゴリズムに利点は無いので使用しないが、後のアイデアのために残しておく.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define N_MAX 10001

Int N;
vector<Int> A(N_MAX, -1);
vector<Int> L(N_MAX, 0);

Int solve() {
  Int k, max_ = 0;
  loop(n,1,N+1) {
    k = 0;
    loop(l,0,n) {
      if (A[l] < A[n] && L[k] < L[l]) {
        k = l;
      }
    }

    L[n] = L[k] + 1;
    if (L[n] > max_) max_ = L[n];
  }

  return max_;
}

int main(void) {
  cin >> N;
  loop(i,0,N) cin >> A[i];
  cout << solve() << endl;
}