动态规划、背包问题入门

目录

  • 1、动态规划定义
  • 2、数塔问题
    • 题目描述:
    • 思路:
    • 代码实现:
  • 3、最长有序子序列
    • 问题描述:
    • 代码实现:
  • 动态规划基本思想
    • 特点
  • 4、背包问题
    • ①01背包问题
      • 空间复杂度优化
    • ②完全背包
    • ③多重背包
      • 二进制优化
    • ④二维费用背包

1、动态规划定义

动态规划是一种用于解决优化问题的算法策略,它的核心是把一个复杂的问题分解为一系列相互关联的子问题,并通过求解子问题的最优解来构建原问题的最优解。
它将一个问题分解为若干个子问题,然后从最简单的子问题开始求解,逐步推导出更复杂的子问题的解,最终得到原问题的最优解。动态规划的关键是找到子问题之间的递推关系,以及确定合适的边界条件和初始值。动态规划通常需要保存已解决的子问题的答案,以避免重复计算,节省时间。

2、数塔问题

数塔问题为动态规划经典入门题目

题目描述:

一个由数字组成的三角形结构,要求从顶部开始向下走,每次只能走到相邻的位置,最终到达底部,使得经过的数字之和最大。数塔问题可以用一个二维数组来表示,其中第i行有i个元素,表示第i层的数字。例如:

5(表示总共有五行)
9
12 15
10 6 8
2 18 9 5
16 12 18 10 8

则经过的结点的数字之和最大是多少?

思路:

从上向下分析,从下向上实现

代码实现:

#include
#include                   
#include  
#include
#include
using namespace std;
#define maxn 105
int dp[maxn][maxn],num[maxn][maxn];//dp存储每个节点到底层的最大路径和
int main()
{
	int n,c;
	scanf("%d",&c);
	while(c--)
	{
		scanf("%d",&n);
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)//注意这个代码从i=1开始,与下面配套(i=0也可以)
		{
			for(int j=1;j<=i;j++)
			 scanf("%d",&num[i][j]);//num存储每个节点的值
		} 
		for(int i=1;i<=n;i++)
		 dp[n][i]=num[n][i];
		for(int i=n-1;i>=1;i--)
		{
			for(int j=1;j<=i;j++)
			{
				dp[i][j]=max(num[i][j]+dp[i+1][j],num[i][j]+dp[i+1][j+1]);//加上左节点的值或右节点的值(最大)
			}
		}
		printf("%d\n",dp[1][1]);//与上面i=1呼应;dp[1][1]为三角形顶点到底层的最大路径和,即该结构的最大路径和
	}
	return 0;
}

其中dp[i][j]=max(num[i][j]+dp[i+1][j],num[i][j]+dp[i+1][j+1])是状态转移方程,为动态规划最重要的体现也是其最重要的特征,他将子问题与原问题通过状态转移方程联系在了一起。

3、最长有序子序列

问题描述:

对于给定一个数字序列 (a1​,a2​,…,an​) ,如果满足a1​ 求出给定数字序列中的最长有序子序列的长度。

7(序列长为7)
1 7 3 5 9 4 8

代码实现:

#include 
using namespace std;
#define endl '\n'
#define int long long 
const int N=100010;
int a[N],dp[N];
signed main()
{
    int t;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        int n;
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++)
        {
            dp[i]=1;//dp[i]存储以第i个数结尾的最长上升子序列长度,开始为1
            for(int j=1;j<i;j++)
            {
                if(a[j]<a[i])
                {
                    dp[i]=max(dp[i],dp[j]+1);//如果前面有比这个数小的,就接在它后面
                }
            }
        }
        int res=0;
        for(int i=1;i<=n;i++) res=max(res,dp[i]);
        if(i>1) cout<<endl;
        cout<<res<<endl;
    }
    return 0;
}

动态规划基本思想

如果各个子问题不是独立的(即为重复的),不同的子问题的个数只是多项式量级(即有限的),如果我们能够保存已经解决的子问题的答案(数组),而在需要的时候再找出已求得的答案,这样就可以避免大量的重复计算。

特点

1.最优化原理(最优子结构性质) 最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质。

2.无后效性 将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

3.子问题的重叠性 动态规划将原来具有指数级复杂度的搜索算法改进成了具有多项式时间的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。 动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。

4、背包问题

①01背包问题

给定一个容量为的背包和个物品。每个物品()都有一个正整数重量和一个正整数价值。决策目标是在满足背包容量限制的条件下,确定将哪些物品放入背包,使得放入背包中的物品总价值最大。并且对于每个物品而言,只有两种选择,要么将整个物品放入背包(用1表示),要么不放入背包(用0表示),不能只放入物品的一部分。

思考:在每个物品都有可能被选中的前提下,该如何构造“子问题”
将无序变为有序:依次考虑前1个、前2个…前i个物品
状态定义:f[i][v]表示前i件物品放入一个容量为v的背包可以获得的最大价值

例题:
五件物品,背包容量为10,物品价值分别为1,2,3,4,5;体积分别为5,4,3,2,1
动态规划、背包问题入门_第1张图片
例题中物品价值与体积纯属巧合,该题不用排序,与物品价值与体积无本质关系,重点在状态转移方程

空间复杂度优化

将二维数组变为一维,将第i件物品的结果覆盖前i-1件计算的结果即可
dp[ j ] = max( dp[ j ] , dp[ j-v[ i ] ] + w[ i ] )
但这样会有错误,回到上面的例题,当取到第一件物品(1,5)时,计算背包容量为10的数据时,我们看到状态转移方程中,当背包容量10减去体积5时还剩余5个单位的容量,此时dp[ 5 ] + 1 = 2,即第一件物品被两次放入了背包,但这与01背包问题相违背,物品只有一件
解决方法:
逆向遍历
我们先解决背包容量为10时的问题就可以避免重复
伪码:

#include < bits/stdc+ +.h>
using namespace std;
int dp[1001], W|1000], c[1000];
int main(
{
    int num,n, V, i, j; 
    scanf("%d", &num);
    while (num--) {
        scanf("%d %d", &n, &V);
        for (i=0; i<n; i++) scanf("%d", &w[i]);
        for (i=0; i<n; i++) scanf(%d", &c(i]);
        memset(dp, 0, sizeof(dp));
        for (i=0; i<n; i++) 
            for (j=V; j> =c|i]; j--)
                dp|j] = max(dp|j], dp[j-c[i]] + w[i]);
        printf(%d\n", dp[V]);
    }
}

②完全背包

完全背包问题是背包问题中的一种类型。给定一个容量为的背包和种物品,每种物品()都有一个重量和一个价值。与 0 - 1 背包问题不同的是,每种物品的数量是无限的,可以选择放入背包中任意多个(包括 0 个)该种物品。
目标是在背包容量允许的情况下,选择放入背包的物品组合,使得背包内物品的总价值最大。例如,有一个容量为的背包,有两种物品,物品重量为、价值为,物品重量为、价值为。可以多次选择物品和物品放入背包,只要背包能装得下,要找出一种放置方式使得总价值最大。

将01背包一维数组的逆序改为顺序即可
思考:要求放入物品必须完全填补背包容量,否则无法放入
即添加合法性判断(if)
将数组初始化为-1即可,若剩余容量所对应的值为-1则无法放入

③多重背包

介于01背包与完全背包之间

多重背包问题是背包问题的一个变体。给定一个容量为的背包、种物品。对于每种物品(),有其重量、价值,还有一个数量限制(表示物品最多能放入背包中的个数)。
目标是在满足背包容量的限制下,选择合适的物品放入背包,使得背包内物品的总价值最大。例如,有一个容量为 10 的背包,有 3 种物品:物品 1 重量为 3、价值为 4、数量限制为 2;物品 2 重量为 2、价值为 3、数量限制为 3;物品 3 重量为 1、价值为 2、数量限制为 4。要找出一种物品放置策略,在不超过背包容量的情况下,使总价值最大。

第i个物品有j个就将其转化为j个相同的第i件物品就行
例如第一件物品有三个,则将其变为三个物品,即第1、2、3件物品都相同即可转为01背包问题

二进制优化

如果每件物品有1w件甚至更多时,问题规模就太大了,所有对其时间复杂度进行优化
运用二进制进行物品拆分
例如有一件物品价值为2,体积为1,有100件
可将其分别转化为(2,1)(4,2)(8,4)(16,8)(32,16)(64,32)(74,37)七件物品
第七件物品计算:前六件物品共63份,所以第七件物品为100-63 = 37份

int t = 1;
while (x>=t)
{
    v[cnt] = a*t;
    c[cnt++] = b*t;
    x -= t;//x为剩余数量
    t <<= 1;//t左移一位(*2)
}
if (x)
{
    v[cnt] = a*x;
    c[cnt++]= b*x;
}

④二维费用背包

二维费用背包问题是背包问题的一种扩展形式。在这个问题中,每件物品有两种不同的 “费用”(可以理解为两种不同的资源消耗维度)和一个价值。
给定背包的两种容量限制,分别设为和,以及个物品。对于每个物品(),有第一种费用、第二种费用和价值。
目标是在两种费用(资源消耗)都不超过背包容量限制的情况下,选择物品放入背包,使得背包内物品的总价值最大。例如,考虑一个旅行背包问题,一种 “费用” 可以是背包的重量限制(设为),另一种 “费用” 可以是背包的体积限制(设为)。每个物品有自己的重量、体积和价值,需要在不超过重量和体积限制的条件下选择物品放入背包,使总价值最大。

费用增加一维,状态也增加一维即可
设f[ i ][ v ][ u ]表示前i件物品付出两种代价分别为v和u时可获得的最大价值
状态转移方程:
f[ i ][ v ][ u ] = max( f[ i-1 ][ v ][ u ] , f[ i - 1 ][ v - a[ i ] ][ u-b[ i ] ] + w[ i ] )

你可能感兴趣的:(动态规划,代理模式,算法,笔记,c语言)