动态规划法求最长上升子序列(LIS)

最长上升子序列(LIS)是指一个序列中最长的单调递增的子序列,对于任意的i<j都满足ai<aj的子序列。

下面我们来介绍两种dp来求LIS。

方法1:

我们首先来建立一下递推关系:

定义dp[i]:为以ai为末尾的最长上升子序列的长度。

ai 结尾的上升子序列是:

(1)只包含 ai 的子序列

(2)在满足 j<i 并且 aj<ai 的以 aj 为结尾的上升子序列末尾,追加 ai 后得到的子序列

这二者之一。这样就可以建立如下递推关系:

dp[i]=max{1,dp[j]+1 j<i且 aj<ai };

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int dp[maxn];
int a[maxn];
int main()
{
    int n;
    int res = -1;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
    {
        dp[i] = 1;
        for (int j = 1; j < i; j++)
        {
            if (a[j] < a[i])
                dp[i] = max(dp[i], dp[j] + 1);
        }
                res=max(res,dp[i]);
    }
    cout << res;
    return 0;
}    

这个算法的复杂度为O(n2),当数据量过多时会超时,下面介绍复杂度为O(nlogn)的算法。

方法2:

 首先我们来定义dp[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素。
注意dp中元素是单调递增的,我们在下面要用到这个性质

len为LIS的长度,首先让len=1,dp[1]=a[1], 然后i从2开始,对于a[i],如果

(1) a[i]>dp[len],那么此时可以直接把a[i]接到d[len]的后面,并且长度加一。即d[++len]=a[i]。

(2) a[i]<dp[len],那么我们就在dp这个数组中找到第一个比a[i]大

的数,并用a[i]替换这个数,此时数组dp仍然保持递增且长度不变。

这样我们就维护了数组dp,并且最后的len就是最长上升子序列的长度。算法复杂度为O(nlogn)。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int dp[maxn];
int a[maxn];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int len=1;
    dp[1]=a[1];
    for (int i = 2; i <= n; i++)
    {
           if (a[i] > dp[len])
             dp[++len]=a[i];
           else{
            int j=lower_bound(dp+1,dp+len+1,a[i])-dp;
            dp[j]=a[i]; 
         }
                
    }
    cout<<len;
    return 0;
}    

下面再补充一个求最长不上升子序列的方法,复杂度同样为O(nlogn)。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
int a[maxn], dp[maxn];
bool cmp(int a, int b)
{
    return a > b;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int len = 1;
    dp[1] = a[1];
    for (int i = 2; i <= n; i++)
    {
        if (dp[len] >= a[i])
            dp[++len] = a[i];
        else 
            dp[upper_bound(dp + 1, dp + len + 1, a[i], cmp) - dp] = a[i];
    }
    cout << len;
    return 0;
}

这里由于dp数组里的数递减的,而upper_bound适用于递增数列,因此我们给他重载了比较函数。

这样当 a[i]>dp[len] 时,我们在dp数组中找到最后一个大于a[i]的数(因为非上升子序列,可能会存在相同的数,用upper比较好一点),并替换它。

如果dp里没有比a[i]大的数,就会返回第一个数,这时a[i]替换的就是dp的第一个数,同样满足dp是递减的数组。

这样我们就得到了最长不上升子序列的长度len。

猜你喜欢

转载自www.cnblogs.com/xiaoguapi/p/10024008.html