【动态规划】最长上升子序列(LIS)

今天看了《挑战程序设计竞赛》的动态规划部分,感觉对以前一些知其然却不知其所以然的问题有了更好的理解,先整理一部分。

题意:

有一个长为n的数列 a0,a1,a2,...,an 。请求出这个序列中最长的上升子序列的长度。上升子序列指的是对于任意的 i<j 都满足 ai<aj 的子序列。

分析:

dp[i] 为第 i 个下标之前的子串中最长上升子序列长度。得到递推关系式,时间复杂度 O(n2)

dp[i] = max(dp[i], dp[j] + 1) (a[i] > a[j])

代码:

#include<iostream>
using namespace std;
int a[105], dp[105];
int main (void)
{
    int n, ans = 0; cin>>n;
    for (int i = 0; i < n; i++) cin>>a[i];
    fill(dp, dp + n, 1);
    for (int i = 0; i < n; i++){
        for (int j = 0; j < i; j++){
            if(a[i]>a[j])
                dp[i] = max (dp[i], dp[j] + 1);
        }
        ans = max (dp[i], ans);
    }
    cout<<ans<<endl;
    return 0;
}

分析:

还可以定义 dp[i] 为长度为i的上升子序列中末尾元素的最小值,对于长度相同的子序列,末尾元素越小,最终获得的上升子序列越可能长。从序列头开始对每个元素 a[j] 考虑上升子序列长度为 0...i 的情况,得到递推关系式,时间复杂度 O(n2)

 dp[i] = min(dp[i], a[j]) (a[j]>dp[i-1]||i==1)

代码:

#include<iostream>
using namespace std;
const int INF=0x3fffffff;
int a[105], dp[105];
int main (void)
{
    int n, ans = 0; cin>>n;
    for (int i = 0; i < n; i++) cin>>a[i];
    fill(dp + 1, dp + n + 1, INF);
    dp[0] = a[0];
    for(int j = 0; j < n; j++){
          for(int i = 1; i <= n; i++){
          if(i == 1||a[j] > dp[i-1])
            dp[i] = min(dp[i], a[j]);
        }
    }
    for(int i = n; i >= 1; i--){
        if(dp[i] != INF){
            cout<<i<<endl;
            break;
        }
    }
    return 0;
}

分析:

上述方法中 dp 数组除INF外单调递增,所以对于每个元素,最多只更新一次dp数组, 而对于这次更新的位置,不必挨个遍历,可以直接二分查找下界,将复杂度降到 O(logn)

代码:

#include<iostream>
using namespace std;
const int INF=0x3fffffff;
int a[105], dp[105];
int _binary_search(int l, int r, int num)
{
    while(l < r){ //区间[l,r)
        int mid = l +  (r - l)/2;
        if(dp[mid] >= num) r = mid;
        else l = mid + 1;
    }
    return l;
}//获取下界
int main (void)
{
    int n, ans = 0; cin>>n;
    for (int i = 0; i < n; i++) cin>>a[i];
    fill(dp + 1, dp + n + 1, INF);
    for(int j= 1; j <= n; j++){
        int pos = _binary_search(1, n+1, a[j]);
        dp[pos] = a[j];
    }
     for(int i = n; i >= 1; i--){
        if(dp[i] != INF){
            cout<<i<<endl;
            break;
        }
    }
    return 0;
}

分析:

可以直接使用STL中的lower_bound获取下界。通过dp数组中INF的下界获取上升子序列长度。为方便表示可直接定义 dp[i] 为长度为 i+1 的上升子序列中末尾元素的最小值。有关 lower_bound 之前写过些简单介绍。

代码:

#include<iostream>
using namespace std;
const int INF=0x3fffffff;
int a[105], dp[105];
int main (void)
{
    int n, ans = 0; cin>>n;
    fill(dp, dp + n, INF);
    for (int i = 0; i < n; i++){
        cin>>a[i];
        *lower_bound(dp, dp + n, a[i]) = a[i];
    }
    cout<<lower_bound(dp, dp +n, INF) - dp<<endl;
    return 0;
}

你可能感兴趣的:(dp,动态规划)