【动态规划】最长上升子序列(Longest Increasing Subsequence)问题以及输出具体方案

最长上升子序列

两道模板题(一样的)
洛谷 B3637 最长上升子序列
AcWing 895. 最长上升子序列

题目描述

这是一个简单的动规板子题。

给出一个由 n(n5000) 个不超过 106 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。

最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。

输入格式

第一行,一个整数 n,表示序列长度。

第二行有 n 个整数,表示这个序列。

输出格式

一个整数表示答案。

样例 #1

样例输入 #1

6
1 2 4 1 3 4

样例输出 #1

4

提示

分别取出 1234 即可。


标准模版代码

#include <iostream>

using namespace std;

const int N = 5010;//洛谷板子题是5000所以开大点

int n;
int f[N], a[N];

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++) 
    {
        scanf("%d", &a[i]);
        f[i] = 1;//初始子序列只有一个字母时长度为1
    }

    for (int i = 0; i < n; i++)
        for (int j = 0; j < i; j++)
        {
            if (a[j] < a[i]) //上升
                f[i] = max(f[i], f[j] + 1);//最长
        }

    int res = 0;
    for (int i = 0; i < n; i++) res = max(res, f[i]);
    printf("%d", res);
    return 0;
}

二分+贪心优化

#include <iostream>
#include <vector>

using namespace std;

const int N = 1e5 + 10;

int n;
int a[N];
vector<int> q;//因为二分后要根据下标修改元素,
//而双端队列deque不能根据下标修改元素,所以不能用deque
//而是用vector来模拟可根据下标修改元素的特殊队列

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    for (int i = 0; i < n; i++)
    {
        if (q.empty() || a[i] > q.back()) q.push_back(a[i]);
        else if (a[i] <= q.back())
        {
            int l = 0, r = q.size() - 1;
            while (l < r)
            {
                int mid = l + r >> 1;
                if (q[mid] >= a[i]) r = mid;
                else l = mid + 1;
            }
            q[r] = a[i];
        }
    }
    cout << q.size();
    return 0;
}

输出具体方案

代码解释1int maxLen = *max_element(f.begin(), f.end());

// 代码整体功能:
// 这段代码的目的是在一个整数容器 f 中找到最大的元素,并将其存储在 maxLen 变量中。

// 代码解释:
// 首先,我们使用了标准库中的 max_element 函数。
// max_element 函数接受两个迭代器作为参数,这里是 f.begin()f.end()
// f.begin() 表示容器 f 的起始迭代器,f.end() 表示容器 f 的结束迭代器。
// 这个函数会遍历 f 容器中的元素,从 f.begin() 开始,直到 f.end() 之前的元素。
// 然后,它会找出这些元素中的最大值。

// 接着,max_element 函数返回一个迭代器,该迭代器指向容器中最大元素的位置。
// 由于 max_element 函数返回的是一个迭代器,而我们想要的是元素的值,
// 所以在函数调用前使用 * 运算符进行解引用操作。
// 这将迭代器指向的元素的值提取出来,并存储在 maxLen 变量中。

代码解释2int k = find(f.begin(), f.end(), maxLen) - f.begin();

// 代码整体功能:
// 这段代码的目的是在容器 f 中查找元素 maxLen 的位置,并将该位置存储在变量 k 中。

// 代码解释:
// 首先,使用 find 函数来查找元素。find 函数接受三个参数:
// 1. 起始迭代器 f.begin(),表示从容器 f 的开始位置开始查找。
// 2. 结束迭代器 f.end(),表示查找范围截止到容器 f 的末尾位置(不包括 f.end() 所指向的元素)。
// 3. 要查找的元素 maxLen,它是之前代码中找出的容器 f 中的最大元素。

// find 函数会在 f.begin()f.end() 的范围内查找第一个等于 maxLen 的元素。
// 如果找到了,find 函数会返回一个迭代器,该迭代器指向找到的元素。
// 如果没找到,find 函数会返回 f.end()

// 然后,通过 find(f.begin(), f.end(), maxLen) - f.begin() 计算元素 maxLen 在容器中的位置:
// 用 find 函数返回的迭代器减去 f.begin() 迭代器,得到的结果是一个整数,表示元素 maxLen
相对于容器 f 起始位置的偏移量。
// 这个偏移量存储在变量 k 中。

代码解释3cout << path[i] << " \n"[i == path.size() - 1];

" \n"[i == path.size() - 1];:这是一个比较巧妙的写法。" \n"是一个字符串常量,它包含一个空格和一个换行符。
[i == path.size() - 1]是一个条件表达式,当i等于path.size() - 1(即遍历到最后一个元素)时,表达式的值为 1,此时取字符串" \n"中的第二个字符(即换行符\n);否则表达式的值为 0,取字符串" \n"中的第一个字符(即空格)。这样做的效果是,除了最后一个元素输出后换行,其他元素输出后都跟一个空格。

输出具体序列(DP做法,STL版本)

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int main()
{
    int n = 0;
    cin >> n;

    vector<int> a(n, 0); // 原数组
    vector<int> f(n, 1); // 状态
    vector<int> g(n, 0); // 记录状态转移

    for (int i = 0; i < n; i++) cin >> a[i];

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < i; j++)
        {
            if (a[j] < a[i])
            {
                if (f[i] < f[j] + 1)
                {
                    f[i] = f[j] + 1;
                    g[i] = j; // 记录i是由j更新的
                }
            }
        }
    }

    int maxLen = *max_element(f.begin(), f.end());
    int k = find(f.begin(), f.end(), maxLen) - f.begin();

    vector<int> path; // 存具体方案
    for (int i = 0; i < maxLen; i++)
    {
        path.push_back(a[k]);
        k = g[k];
    }

    reverse(path.begin(), path.end());
    for (int i = 0; i < path.size(); i++)
    {
        cout << path[i] << " \n"[i == path.size() - 1];
    }
    return 0;
}

输出具体序列(DP做法,数组版本)

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

const int N = 5010;

int n;
int a[N], f[N], g[N]; // 分别为原数组、状态、记录转态转移
int path[N]; // 记录具体方案

int main()
{
    cin >> n;

    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
        f[i] = 1;
    }

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < i; j++)
        {
            if (a[j] < a[i])
            {
                if (f[i] < f[j] + 1)
                {
                    f[i] = f[j] + 1;
                    g[i] = j; // 记录i是由j更新的
                }
            }
        }
    }

    int maxlen = 0, maxindex;
    for (int i = 0; i < n; i++)
    {
        if (maxlen < f[i])
        {
            maxlen = f[i]; // 记录最大长度
            maxindex = i; // 记录最大值下标
        }
    }

    for (int i = 0; i < maxlen; i++)
    {
        path[i] = a[maxindex];
        maxindex = g[maxindex]; // 将当前下标更新为上一个下标
    }
    reverse(path, path + maxlen);

    for (int i = 0; i < maxlen; i++)
    {
        cout << path[i] << " \n"[i == maxlen - 1];
    }
    return 0;
}
posted @   Tshaxz  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
Language: HTML
点击右上角即可分享
微信分享提示