【专题】最长不下降序列(LIS)

题目描述就不写了,这题实在太经典了。

下面重点分析算法:

大家都知道,这题是一道动态规划的入门题。众所周知,动态规划的题都可以通过搜索骗分获得一定的分数。所以,对于这道题,我们仍然可以先用 d f s dfs dfs写出来。代码较为暴力好懂,就不做解释。这个代码可以获得40-60分(视不同的OJ而定)

#include <iostream>
#include <cstring>
const int N = 109;
int n = 1, mx = -1, a[N], ans[N], anss[N];
void dfs(int x, int cnt)
{
    if (x > n)
        return ;
    if (cnt > mx)
    {
        mx = cnt;
        memcpy(anss, ans, N);
        // printf("mx=%d\n", mx);
        // for (int i = 1; i <= mx; i++)
        //     printf("%d ", ans[i]);
        // puts("");
        // puts("----------");
        // 这个注释可供大家看看这个程序的处理过程,以便小白更好的理解这段代码
    }
    for (int i = x; i <= n; i++)
        if (a[i] > ans[cnt])
        {
            ans[++cnt] = a[i];
            dfs(i+1, cnt);
            ans[cnt--] = 0;
        }
}
int main()
{
    while (~scanf("%d", a + n)) n++; n--;
    dfs(1, 0);
    printf("%d\n", mx);
    for (int i = 1; i <= mx; i++)
        printf("%d ", anss[i]);
    return 0;
}

测试样例:

测试样例

第一步. 确定状态

通常处理数列的题目, 我们都有现成的状态划分, 即从第一个数开始, 到第 n n n个数结束.
f [ i ] 代 表 前 i 个 数 中 的 最 长 不 下 降 子 序 列 的 长 度 f[i]代表前i个数中的最长不下降子序列的长度 f[i]i
这时候有个问题: 第 i i i个数我到底是取它还是舍它? 如果不包含 i i i, 那就没法写出状态转移方程.

不妨改成:
f [ i ] 为 前 i 个 数 的 最 长 不 下 降 序 列 的 长 度 , 必 须 以 第 i 个 数 结 尾 f[i]为前i个数的最长不下降序列的长度, 必须以第i个数结尾 f[i]i,i
第二步. 状态转移方程

为了方便描述, 我们将输入定为为 a a a数组, 储存动态规划结果数组定义为 f f f数组

根据状态, 我们推导出状态转移方程: f [ i ] = m a x ( f [ j ] ) + 1 f[i]=max(f[j])+1 f[i]=max(f[j])+1, 其中 a [ j ] < a [ i ] 且 j < i a[j]<a[i]且j<i a[j]<a[i]j<i. 原因可以通过图示来说明.

易得 f [ 1 ] = 1 f[1]=1 f[1]=1, 因为它独立成为一个最长不下降子序列

因为 7 < 13 7<13 7<13, 所以不存在 a [ j ] < a [ i ] 且 j < i a[j]<a[i]且j<i a[j]<a[i]j<i.

此时, 存在 7 < 9 7<9 7<9, 所以 m x = f [ 2 ] = 1 mx=f[2]=1 mx=f[2]=1. f [ 3 ] = m x + 1 = 2 f[3]=mx+1=2 f[3]=mx+1=2.

此时, 存在 7 < 16 , 9 < 16 7<16,9<16 7<16,9<16, 所以 m x = m a x ( f [ 2 ] , f [ 3 ] ) = f [ 3 ] = 2 mx=max(f[2],f[3])=f[3]=2 mx=max(f[2],f[3])=f[3]=2. f [ 4 ] = m x + 1 = 3 f[4]=mx+1=3 f[4]=mx+1=3

以此类推… 直至算到 f [ 14 ] f[14] f[14], 结束循环. 此时, max{f[i]}即为答案.

因此, 有以下代码:

#include <iostream>
const int N = 109;
int n = 1, ans, a[N], f[N];
int main()
{
    while (~scanf("%d", a + n)) n++; n--;
    for (int i = 1; i <= n; i++)
    {
        int mx = 0, id = 0;
        for (int j = 1; j < i; j++)
            if (a[j]<a[i] and mx<f[j])
                mx = f[j];
        f[i] = mx + 1;
        if (f[i] > ans)
            ans = f[i];
    }
    printf("%d\n", ans);
    return 0;
}

部分OJ还要求输出最长不下降序列. 这似乎很难写, 实际上我们只需要加一个数组和一个变量就可以搞定.

为了方便描述, 我们将元素 i i i的前驱定为 p r e [ i ] pre[i] pre[i].

找出所有满足 a [ j ] < a [ i ] 且 j < i a[j]<a[i]且j<i a[j]<a[i]j<i j j j. 求出max{a[j]}的下标 i d id id, 那么记录 p r e [ i ] = i d pre[i]=id pre[i]=id. 可以通过图示来帮助更好的理解.

p r e [ 1 ] pre[1] pre[1]没有前驱, 故 p r e [ 1 ] = 0 pre[1]=0 pre[1]=0.

7 7 7之前没有比它大的, 所以 p r e [ 2 ] = 0 pre[2]=0 pre[2]=0.

9 9 9之前有 7 < 9 7<9 7<9, 所以 p r e [ 3 ] = m a x { a [ j ] } 的 下 标 = a [ 2 ] 的 下 标 = 2 pre[3]=max\{a[j]\}的下标=a[2]的下标=2 pre[3]=max{a[j]}=a[2]=2.

16 16 16之前有 7 < 16 , 9 < 16 7<16,9<16 7<16,9<16, 所以 p r e [ 4 ] = m a x { a [ j ] } 的 下 标 = a [ 3 ] 的 下 标 = 3 pre[4]=max\{a[j]\}的下标=a[3]的下标=3 pre[4]=max{a[j]}=a[3]=3.

以此类推… 直至 p r e [ 14 ] pre[14] pre[14], 然后递归 p r i n t ( p r e [ m a x { f [ i ] 的 下 标 } ] ) print(pre[max\{f[i]的下标\}]) print(pre[max{f[i]}]), 直至 x = 0 x=0 x=0结束输出.

因此, 有如下代码: (代码中的 s i d sid sid即上文分析中的 i d id id)

#include <iostream>
const int N = 109;
int n = 1, ans, sid, a[N], f[N], pre[N];
void print(int x)
{
    if (x == 0)
        return ;
    print(pre[x]);
    printf("%d ", a[x]);
}
int main()
{
    while (~scanf("%d", a + n)) n++; n--;
    for (int i = 1; i <= n; i++)
    {
        int mx = 0, id = 0;
        for (int j = 1; j < i; j++)
            if (a[j]<a[i] and mx<f[j])
                mx = f[j], id = j;
        f[i] = mx + 1, pre[i] = id;
        if (f[i] > ans)
        {
            ans = f[i];
            sid = i;
        }
    }
    printf("%d\n", ans);
    print(sid);
    return 0;
}

#创作不易, 赏个赞呗#

### C++ 实现最长上升子序列 (LIS) 算法 #### 示例代码解释 对于给定的一个整数数组 `a`,目标是找出其中最长的严格递增子序列并返回该序列的最大长度。下面展示了一种基于动态规划的方法来解决问题。 ```cpp #include <iostream> #include <algorithm> // For std::max function using namespace std; int main() { int a[1005], dp[1005], ans = 0; int n; cin >> n; // 输入数组大小 for (int i = 1; i <= n; ++i) { cin >> a[i]; // 输入数组元素 } for (int i = 1; i <= n; ++i) { dp[i] = 1; // 初始化dp数组,默认每个位置至少可以构成长度为1的上升子序列 for (int j = 1; j < i; ++j) { if (a[j] < a[i]) { // 如果存在更早的位置上的数值小于当前位置,则更新dp值 dp[i] = max(dp[i], dp[j] + 1); } } ans = max(ans, dp[i]); // 更新最大上升子序列长度 } cout << ans; // 输出最终的结果 return 0; } ``` 这段程序首先读取输入的数据到数组 `a[]` 中,并初始化另一个同样大小的辅助数组 `dp[]` 来记录到达每一个索引处所能形成的最长上升子序列的长度。遍历整个数组,在每一步都尝试寻找之前已经访问过的所有可能形成新的上升子序列的情况,并据此调整当前节点对应的 `dp[]` 值。最后输出的是在整个过程中发现的最大上升子序列长度[^1]。 此方法的时间复杂度为 O(),适用于较小规模的数据集(n ≤ 1e4)。当面对更大范围内的数据时,建议采用更加高效的算法比如贪心加二分查找的方式来进行优化处理[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值