以最长不递增子序列为例:
1.不递增,即表示,子序列中某元素小于等于上一个元素;
例如:{389,207,155,300,299,170,158,65},其中最长不递增子序列为{389,300,299,170,158,65},该子序列长度为6;
2.动态规划求子序列长度:
思路:
1.用a数组存储待求元素;
2.定义一维数组dp[i]:dp[i]表示以a[i]结尾的最长不递增子序列的长度
3.初始化dp数组为1:每个元素结尾位置的递增子序列长度至少为1
3.双层循环判断:
(1):外层循环(i)遍历a数组中的每一个元素,内层循环(j)遍历该元素之前的所有元素;
(2):判断外层循环的当前元素a[i],是否小于等于之前的每一个元素a[j](j这个位置加一(dp[j]+1);
(3):用max函数比较这一次判断更新之后与之前计算的值,dp[i]=max(dp[i],dp[j]+1);
3.在上一个代码的基础上,回溯输出整个序列:
思路:
1.在上一个代码的for循环中,加入初始化为-1的prev数组,逐个记录每个元素为结尾的子序列的前一个元素的下标,若该子序列只有一个元素,则仍为-1;
注:仍然以a={389,207,155,300,299,170,158,65}的最长不递增子序列为例,其所获得的dp应该为dp={1,2,3,2,3,4,5,6},prev={-1,0,1,0,3,4,5,6};
2.遍历dp数组找最长子序列末尾元素的位置;
3:回溯得到最长子序列;
4:反转,输出;
附完整代码:
注:最长不递减子序列思路同上,代码把a[i]<=a[j]改成a[i]>=a[j]即可;
#include
using namespace std;
int main()
{
int n = 0;
cin >> n;
vector a(n, 0);
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
vector dp(n, 1), prev(n, -1);
// 初始化动态数组,每个位置至少为1
// prev:记录在动态规划的过程中,构成子序列的元素的前一个元素的索引,初始化为-1;
for (int i = 0; i < n; i++)
{ // 遍历数组,a[i]:当前元素
for (int j = 0; j < i; j++)
{ // 遍历当前元素之前的元素
// a[j]:之前的元素
if (a[i] <= a[j])
{// 如果当前元素小于等于之前的元素
dp[i] = max(dp[i], dp[j] + 1); // 更新dp数组
// dp[i]表示以当前元素结尾的最长递减子序列的长度
// dp[j]+1表示左侧所有元素的最长递减子序列长度+当前元素
prev[i] = j; // 记录以当前元素为结尾的最长递减子序列的前一个元素的下标
}
}
}
int len = prev.size();
cout << "prev:";//prev数组值
for (int i = 0; i < len; i++)
{
cout << prev[i] << " ";
}
//-1,0,1,0,3,4,5,6
cout << endl;
cout << "dp:";//dp数组值
int len2 = dp.size();
for (int i = 0; i < len2; i++)
{
cout << dp[i] << " ";
}
// 1,2,3,2,3,4,5,6
cout << endl;
//子序列长度
cout << "len: " << *max_element(dp.begin(), dp.end()) << endl;
// 输出最长递减子序列
int max_index = max_element(dp.begin(), dp.end()) - dp.begin();
// 前者返回一个迭代器,指向容器中第一个最大值的位置
// 用它减去dp.begin(),得到的是dp.begin()到max_element(dp.begin(), dp.end())之间的距离
// 也就是最大值的位置
// 所以这个式子找到dp数组中的最大值的位置
// 这个位置就是最长递减子序列的最后一个元素的位置
vector res;
while (max_index != -1) // 当这个存着最大值的元素的前一个元素位置不为-1时
{
res.push_back(a[max_index]); // 将当前元素加入结果数组
max_index = prev[max_index]; // 更新当前元素的前一个元素位置(回溯)
}
reverse(res.begin(), res.end());
// 因为是从子序列末位开始向前寻找的,所以需要将结果数组反转
//输出
cout << "arr:";
for (auto i : res)
{
cout << i << " ";
}
return 0;
}
思路:
1.数组a存储待判断元素,准备一个数组d,记录结果子序列
2.遍历数组a,如果当前元素小于等于d的最后一个元素,则加入d的末尾
注:lower_bound适用于有序数组,此处求最长不递增子序列,需要找最小插入位置,故用greater
3.如果当前元素大于d的最后一个元素,则用lower_bound找到d中第一个大于等于a[i]的元素,替换它
注:如果求最长上升子序列:
1.把a[i] <= d.back()改为a[i] > d.back();
2.将函数中的greater去掉;
3:将upper_bound改成lower_bound;
附题:P1020 [NOIP 1999 提高组] 导弹拦截 - 洛谷 | 计算机科学教育新生态
例题代码:
思路:
1.最长不上升+最长上升
2.注意upper_bound和lower_bound的混淆!!!!!!!!
#include
using namespace std;
int main()
{
int n = 0;
vector a;
while (cin >> n)
{
a.push_back(n);
}
vector d; // 记录最长不上升子序列
for (int i = 0; i < a.size(); i++)
{
if (d.empty() || a[i] <= d.back())
d.push_back(a[i]);
else
{
auto it = upper_bound(d.begin(), d.end(), a[i], greater());
// 第四个参数greater()表示从大到小排序
*it = a[i];
}
}
cout << d.size() << endl;
// 求最长上升子序列
d.clear();
for (int i = 0; i < a.size(); i++)
{
if (d.empty() || a[i] > d.back())
d.push_back(a[i]);
else
{
auto it = lower_bound(d.begin(), d.end(), a[i]);
*it = a[i];
}
}
cout << d.size() << endl;
}