1. 程式人生 > 其它 >最長公共子序列(LIS)

最長公共子序列(LIS)

題目描述

時間限制:1.0s 記憶體限制:256.0MB

問題描述

   給定一個長為\(n\)的序列,求它的最長上升子序列的長度。

輸入格式

   輸入第一行包含一個整數\(n\)
   第二行包含\(n\)個整數\(a_1,a_2,…,a_n\),為給定的序列。

輸出格式

   輸出一個非負整數,表示最長上升子序列的長度。

樣例輸入

 5
 1 3 2 5 4

樣例輸出

 3

資料規模和約定

   \(0<n\leq10^5\),每個數不超過\(10^6\)

解析

   所謂最長上升子序列,就是給定一列數,求序列中嚴格上升的子序列,子序列中數的位置不一定連續。

1.動態規劃dp

   這是動態規劃中的一個經典應用題目,動態規劃的思想就是把問題分解為一些本質上還是相同問題的小問題,這些小問題的區別僅在於輸入的引數不同,而每一個小問題的解都可以由比他引數更小的一些小問題的解推出,最小的小問題有顯而易見的解,這被稱之為邊界。最長上升子序列(LIS)問題也是這樣。
   \(a_i\)\(1\leq i<n\))表示序列中第\(i\)個整數
   \(dp_i\) 表示以\(a_i\)為結尾的最長上升子序列的長度
   於是,\(dp_i\)要把\(a_i\)加到以\(a_j\)\(1\leq j<i\))為上升子序列末尾的後面,並且符合\(a_j<a_i\)

,找一個最大的dp[j],然後令\(dp_i = dp_j + 1\)即求出了\(dp_i\)
   最後在所有的\(dp_i\)中取一個最大的即為整個序列的最長上升子序列的長度。
   演算法時間複雜度為\(O(n^2)\),當範圍大一些的時候是有些低效的。

#include <iostream>
#include <algorithm>
using namespace std;
int a[100005], d[100006];
const int INF = 0x3f3f3f3f;
int main() {
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	int maxx = -INF;
	for (int i = 1; i <= n; i++) {
		d[i] = 1;
		for (int j = 1; j < i; j++)
			if (a[j] < a[i])
				d[i] = max(d[i], d[j] + 1);
		maxx = max(maxx, d[i]);
	}
	cout << maxx << endl;
	return 0;
}
2.二分搜尋優化

   \(a_i\)\(1\leq i<n\))表示序列中第\(i\)個整數
   \(dp_m\) 表示長度為\(m\)的最長公共子序列的末尾最小值
   當\(i=1\)時,\(dp_i=a_i\)
   當\(i\)逐漸增加,每次掃過\(a_i\),因為\(dp_m\)代表長度為i的最長上升子序列的最小末尾,這可以代表一個長度為m的最長上升子序列,我們考慮可以把\(a_i\)加到哪個最長上升子序列後面構成一個長度\(+1\)的新的最長上升子序列。那麼我們就可以在\(dp\)陣列中尋找到\(j\)\(1\leq j\leq m\))並且\(dp_j<a_i\leq dp_{j+1}\),把\(a_i\)加到這個數代表的序列後面:\(dp_{j+1}=a_i\)
   因為\(dp\)陣列是有序的,所以在進行尋找的時候就可以用二分搜尋來進行。而當我們把\(a\)陣列全部掃描完之後,\(dp\)陣列中的最後一個有效的數字的位置即是我們所求的最長上升子序列的長度\(m\)
   由於迴圈中的搜尋變成了二分搜尋,時間複雜度為\(O(log_n)\),所以整體時間複雜度是\(O(nlog_n)\)

#include <iostream>
#include <algorithm>
using namespace std;
int dp[100005], a[100005];
int b_search(int x, int l, int r) {
    while (l < r) {
        int mid = (r + l) / 2;
        if (dp[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main(int argc, char** argv) {
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {
    	cin >> a[i];
    	dp[i] = 0x3f3f3f3f;
    }
    int m = 1;
    dp[1] = a[1];
    for (int i = 2; i <= n; i++) {
        if (a[i] > dp[m]) dp[++m] = a[i];
        else {
        	int j = b_search(a[i], 1, m);
        	dp[j] = min(dp[j], a[i]);
        }
    }
    cout << m << endl;
    return 0;
}

   在第二個演算法中我們可以通過在二分查詢中改變“上確界”和“下確界”,以及在第一個演算法中通過改變符號(“<”和“<=”或“>”、“>=”等),求出最長不下降、不上升、嚴格下降子序列等問題。