动态规划2 · 子序列问题—dp/二分贪心

一:线性动态规划—最长不递增子序列—O(n²)

以最长不递增子序列为例:

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;
}

二:二分贪心—最长不递增子序列—O(nlogn)

思路:

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;
}

你可能感兴趣的:(c++,开发语言,动态规划,贪心算法,算法)