动态规划思路

0

动态规划的思路:当分析前i个对象的取舍时,在约束条件所有可能取值的情况下,先分别求出每一个约束条件取值下子问题的最优解,这样在分析第i+1个对象加入的情况下,就可以直接调用之前已经分析的其中一个或者多个子问题的解,这些子问题的其他解可能不是局部最优的解,但是考虑到第i+1个对象,他们可能就是最后的最优解的组成部分之一。
动态规划大部分也是运用穷举思想,但是:

  • 第一是只会穷举每个子问题的最优解。
  • 第二是通过状态刷新求新的最优解,因为之前子问题的解已经被存储,新的子问题到来时只需要做出O(n)次max比较并且存储新解即可。

动态规划解题步骤

问题抽象化、建立模型、寻找约束条件、判断是否满足最优性原理、找大问题与小问题的递推关系式、填表、寻找解组成。

背包问题

在解决问题之前,为描述方便,首先定义一些变量:Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积,定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值,同时背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选)。

  1. 建立模型,即求max(V1X1+V2X2+…+VnXn);

  2. 寻找约束条件,W1X1+W2X2+…+WnXn<capacity;

  3. 寻找递推关系式,面对当前商品有两种可能性:

  • 包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
  • 还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。

其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i),但价值增加了v(i);

由此可以得出递推关系式:

  • j<w(i) V(i,j)=V(i-1,j)
  • j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

背包问题最优解回溯:

通过上面的方法可以求出背包问题的最优解,但还不知道这个最优解由哪些商品组成,故要根据最优解回溯找出解的组成,根据填表的原理可以有如下的寻解方式:

V(i,j)=V(i-1,j)时,说明没有选择第i 个商品,则回到V(i-1,j);
V(i,j)=V(i-1,j-w(i))+v(i)时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到V(i-1,j-w(i));
一直遍历到i=0结束为止,所有解的组成都会找到。

例题:
在这里插入图片描述
在这里插入图片描述

最优解为V(4,8)=10,而V(4,8)!=V(3,8)却有V(4,8)=V(3,8-w(4))+v(4)=V(3,3)+6=4+6=10,所以第4件商品被选中,并且回到V(3,8-w(4))=V(3,3);
有V(3,3)=V(2,3)=4,所以第3件商品没被选择,回到V(2,3);
而V(2,3)!=V(1,3)却有V(2,3)=V(1,3-w(2))+v(2)=V(1,0)+4=0+4=4,所以第2件商品被选中,并且回到V(1,3-w(2))=V(1,0);
有V(1,0)=V(0,0)=0,所以第1件商品没被选择。

详细代码实现如下:

#include<iostream>
using namespace std;
#include <algorithm>
 
int w[5] = {
    
     0 , 2 , 3 , 4 , 5 };			//商品的体积2、3、4、5
int v[5] = {
    
     0 , 3 , 4 , 5 , 6 };			//商品的价值3、4、5、6
int bagV = 8;					        //背包大小
int dp[5][9] = {
    
     {
    
     0 } };			        //动态规划表
int item[5];					        //最优解情况
 
void findMax() {
    
    					//动态规划
	for (int i = 1; i <= 4; i++) {
    
    
		for (int j = 1; j <= bagV; j++) {
    
    
			if (j < w[i])
				dp[i][j] = dp[i - 1][j];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
		}
	}
}
 
void findWhat(int i, int j) {
    
    				//最优解情况
	if (i >= 0) {
    
    
		if (dp[i][j] == dp[i - 1][j]) {
    
    
			item[i] = 0;
			findWhat(i - 1, j);
		}
		else if (j - w[i] >= 0 && dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
    
    
			item[i] = 1;
			findWhat(i - 1, j - w[i]);
		}
	}
}
 
void print() {
    
    
	for (int i = 0; i < 5; i++) {
    
    			//动态规划表输出
		for (int j = 0; j < 9; j++) {
    
    
			cout << dp[i][j] << ' ';
		}
		cout << endl;
	}
	cout << endl;
 
	for (int i = 0; i < 5; i++)			//最优解输出
		cout << item[i] << ' ';
	cout << endl;
}
 
int main()
{
    
    
	findMax();
	findWhat(4, 8);
	print();
 
	return 0;
}

最长递增子序列问题

思路:

  1. 定义dp的含义,比如这一题,dp是一个数组,dp[i]表示的是,在确定必须选择v[i]的情况下,表示v[0…i]中的最长递增子序列的长度。
  2. 写出递归式或者状态转换方程:dp[i] = max(dp[j]) + 1 当v[i]>v[j]时
  3. 定义最优值,这里的最优值可能是dp中的某一项。如LISLength = max(dp[i])

例题:
计算最少出列多少位同学,使得剩下的同学排成合唱队形

说明:

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1<=i<=K)使得T1<T2<…<Ti-1Ti+1>…>TK。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

注意:不允许改变队列元素的先后顺序 且 不要求最高同学左右人数必须相等
请注意处理多组输入输出!

备注:
1<=N<=3000
输入描述:
有多组用例,每组都包含两行数据,第一行是同学的总数N,第二行是N位同学的身高,以空格隔开

输出描述:
最少需要几位同学出列

示例1
输入:
8
186 186 150 200 160 130 197 200
复制
输出:
4

求解:
这题抛开场景,核心问题是最长递增子序列,可以参考leetcode 最长递增子序列 来理解。

总的来说,就是自左向右求出最长递增子序列的最优值dp数组,自右向左求出最长递减子序列的最优值dp数组,两者对位相加-1,其中的最大值,就是整个合唱队留在场上的人数的最大值,因此也就是出列的最小值。

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    
    
    int n;
    while(cin >> n){
    
    
        // 输入的数组
        int tmp;
        vector<int> v;
        for (int i = 0; i < n; ++i){
    
    
            cin >>tmp;
            v.push_back(tmp);
        }

        // 最长递增子序列
        if (v.empty()) return 0;
        vector<int> dp1(n, 0);
        for (int i = 0; i < n; ++i){
    
    
            dp1[i] = 1;
            for(int j = 0; j <  i ; ++j){
    
    
                if (v[i] > v[j]){
    
    
                    dp1[i] = max(dp1[i], dp1[j]+1);
                }
            }
        }

        // 最长递减子序列
        vector<int> dp2(n, 0);
        for (int i = n - 1; i >= 0; --i){
    
    
            dp2[i] = 1;
            for (int j = n -1; j > i; --j){
    
    
                if (v[i] > v[j]){
    
    
                    dp2[i] = max(dp2[i], dp2[j]+1);
                }
            }
        }


        int maxLength = 0;
        for (int  i = 0; i < n; ++i){
    
    
            if (maxLength < dp1[i] + dp2[i] - 1){
    
    
                maxLength = dp1[i] + dp2[i] - 1;
                //这里的i就是划分中点
            }
        }
        cout << n - maxLength << endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44576259/article/details/120636758
今日推荐