最长不下降子序列(LIS)
在一个数字序列中,找到一个最长的子序列(可以不连续),使得这个子序列是不下降(非递减)的。
题目
例如,现有序列A={1,2,3,-1,-2,7,9}(从下标1开始),它的最长不下降序列是{1,2,3,7,9},长度为5。另外,还有一些子序列是不下降子序列。
分析
令dp[i]表示以A[i]结尾的最长不下降子序列长度。这样对A[i]来说就会有两种可能
1)如果存在A[i]之前的元素A[j](j < i),使得A[j]<=A[i]且dp[j]+1>dpi,那么就把A[i]跟在以A[j]结尾的LIS后面,形成一条更长的不下降子序列(令dp[i]=dp[j]+1)。
2)如果A[i]之前的元素都比A[i]大,那么A[i]就只好自己形成一条LIS,但是长度为1,即这个子序列里面只有一个A[i]。
最后以A[i]结尾的LIS长度就是1)2)中能形成的最大长度。
现有一个序列{1,5,-1,3},其中的元素分别记为A[1]、A[2]、A[3]、A[4]。假设已经知道以A[1]、A[2]、A[3]为结尾的最长不下降子序列分别为{1}、{1,5}、{-1},长度分别为1、2、1。那么如何知道以A[4]结尾的最长不下降子序列及其长度呢?由于必须以A[4]结尾,因此考虑分别把A[4]加到前面以A[1]、A[2]、A[3]结尾的最长不下降子序列后面,看看能不能使以某个Aj为结尾的最长不下降子序列变得更长。
A[4]:喂,A[1]。我可以站在你后面成为更长的LIS
A[1]:我看看,你比我高,当然可以,这样我们组合的新的LIS{1,3}长度就是2了。
A[4]:喂,A[2]。我可以站在你后面成为更长的LIS吗
A[2]:你那么矮,那还是算了,我这里本来长度就有2了,你就算来了也不增加LIS长度。
A[4]:喂,A[3]。我可以站在你后面成为更长的LIS吗
A[3]:你好高啊,当然可以了,站在我后面LIS{-1,3}长度就为2了
这样比较后,A[4]只有加入A[1]或A[3]后面才会形成新的LIS,长度为2.
由此写出状态转移方程:
dp[i] = max{1,dp[j]+1}(j=1,2,…,i-1&&A[j]<A[i])
上面的状态转移方程中隐含边界:dp[i]=1。显然dp[i]只与小于i的j有关,因此只要让i从小到大遍历即可求出整个dp数组。由于dp[i]表示的是以A[i]结尾的LIS长度,因此从整个dp数组中找到最大的那个才是要寻求的整个序列的LIS长度。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100;
int A[N],dp[N];
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &A[i]);
}
int ans = -1; //记录最大的dp[i]
for(int i = 1; i <= n; i++) //按顺序计算出dp[i]的值
{
dp[i] = 1; //边界初始条件(即先假设每个元素自成一个子序列)
for(int j = 1; j < i; j++)
{
if(A[i] >= A[j] && (dp[j] + 1 > dp[i]))
{
dp[i] = dp[j] + 1; //状态转移方程,用以更新dp[i]
}
}
ans = max(ans ,dp[i]);
}
printf("%d", ans);
return 0;
}
8
1 2 3 -9 3 9 0 11
6