DP專題 2 | LIS最長上升子序列 - POJ 2533

這篇來看LIS~上題。

 

POJ - 2533 Longest Ordered Subsequence

 

Description

A numeric sequence of ai is ordered if a1 < a2 < ... < aN. Let the subsequence of the given numeric sequence (a1a2, ..., aN) be any sequence (ai1ai2, ..., aiK), where 1 <= i1 < i2 < ... < iK<= N. For example, sequence (1, 7, 3, 5, 9, 4, 8) has ordered subsequences, e. g., (1, 7), (3, 4, 8) and many others. All longest ordered subsequences are of length 4, e. g., (1, 3, 5, 8).


一個數字序列ai是按照a1<a2<...<aN進行排列的上升序列,對於一個給定的數字序列(a1,a2,...,aN),其子序列爲(ai1,ai2,...,aiK),其中1<=i1<i2<...<iK<=N。

 

例如對於序列(1, 7, 3, 5, 9, 4, 8) ,有多個子序列,如(1, 7), (3, 4, 8),不過最長上升子序列是(1, 3, 5, 8).


Your program, when given the numeric sequence, must find the length of its longest ordered subsequence.

 

給定一個數字序列,計算最長上升子序列的長度。

 

Input

The first line of input file contains the length of sequence N. The second line contains the elements of sequence - N integers in the range from 0 to 10000 each, separated by spaces. 1 <= N <= 1000

第一行輸入一個N,

第二行輸入N個數。

 

Output

Output file must contain a single integer - the length of the longest ordered subsequence of the given sequence.

輸出最長子序列長度。

 

Sample Input

7
1 7 3 5 9 4 8

Sample Output

4

 

LIS是典型的DP題,dp[i]表示以數字a[i]結尾的最長子序列的最大長度,從位置1一直到N,顯然可以採用遞推的方式解決。

 

轉移方程爲dp[i] = max(dp[j]+1,0<j<i且a[j]<a[i]),

結果爲max(dp[i])

 

由於需要遍歷i之前的所有dp[j],所以是一個二重循環,複雜度O(n2)。

 

LIS的轉移方程不那麼直觀,上一篇數字三角形中dp[i]的計算會依賴dp[i-1],這也是很多時候會用到的模式,而LIS需要一個循環才能算出dp[i],依賴dp[j(0<j<i)]。說明dp在轉移時可能形式多樣,dp[i]肯定依賴i之前的dp,但是沒有定式,邏輯可能比較複雜,也因此使得DP算法靈活多變。

 

LIS除了計算最大長度,有時候可能需要記錄最長序列的值,採用一個表記錄即可,path[i]=j表示i的前驅節點是j,其實對於每一個節點,在更新的過程中只可能有一個前驅節點,因此是不會存在問題的。

 

源代碼:

#ifdef __APPLE__
#define printfd printf
#else
#define printfd
#endif

#define MAX_SIZE 1000000

int main()
{
#ifdef __APPLE__
    freopen("test.txt", "r", stdin);
#endif
    int n;
    scanf("%d", &n);

    vector<int> v(n+1, 0);
    for(int i=1; i<=n; ++i){
        scanf("%d", &v[i]);
    }

    int dp[MAX_SIZE] = {0};
    int path[MAX_SIZE] = {0};
    int max = 0;
    int pos = 0;

    for(int i=1; i<=n; ++i){
        //dp[i] = max(0,dp[j]) + 1
        int m = 0;
        for(int j=1; j<i; ++j){
            if(v[j] < v[i]){
                if(dp[j] > m){
                    path[i] = j;
                    //printfd("j->i %d->%d\n", j, i);
                }
                m = std::max(m, dp[j]);
            }
        }
        dp[i] = m + 1;
        if(dp[i] > max){
            pos = i;
        }
        max = std::max(dp[i], max);
        printfd("dp[%d]=%d\n", i, dp[i]);
    }

    printf("%d\n", max);
    while(pos > 0){
        printfd("%d ", pos);
        pos = path[pos];
    }
    printfd("\n");

    return 0;
}

寫完後想到一個擴展題:這裏求的是上升子序列,如果求有序子序列呢?有序是指既可能上升又可能下降的序列,有興趣的同學可以多思考下哈~

 

下一篇我們接着看LCS,最長公共子序列。

 

個人公衆號(acm-clan):ACM算法日常

專注於基礎算法的研究工作,深入解析ACM算法題,五分鐘閱讀,輕鬆理解每一行源代碼。內容涉及算法、C/C++、機器學習等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章