【动态规划】最长上升子序列(Longest Increasing Subsequence)问题以及输出具体方案
最长上升子序列
两道模板题(一样的)
洛谷 B3637 最长上升子序列
AcWing 895. 最长上升子序列
题目描述
这是一个简单的动规板子题。
给出一个由 \(n(n\le 5000)\) 个不超过 \(10^6\) 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
输入格式
第一行,一个整数 \(n\),表示序列长度。
第二行有 \(n\) 个整数,表示这个序列。
输出格式
一个整数表示答案。
样例 #1
样例输入 #1
6
1 2 4 1 3 4
样例输出 #1
4
提示
分别取出 \(1\)、\(2\)、\(3\)、\(4\) 即可。
标准模版代码
#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;
}
输出具体方案
代码解释1:int 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 变量中。
代码解释2:int 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 中。
代码解释3:cout << 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;
}

浙公网安备 33010602011771号