每日总结之动态规划 5.1-01背包+滚动数组优化

目录

算法 

 动态规划dp

01背包 

滚动数组优化

刷题 

 1.金字塔最大价值路径

2. 过河卒

 3.疯狂的采药

算法 

 

 动态规划dp


 

1.核心:最优子结构

大问题的最优解由小问题的最优解推出

2.性质:无后效性

一旦 f(n) 确定,之后直接调用,不再关心f(n)计算过程

3.构成:设计状态(边界,终止条件)+设计动态转移(递推式)

如f(n)由f(n-1)或f(n-2)推来

经典例题:硬币问题 

描述:共1,5,11面值的硬币,求组成15元的时候,使用最少的币数


1.原理:f(n)=min( f(n-1)+1, f(n-5)+1, f(n-11)+1 )

f(n)只与f(n-1),f(n-5),f(n-11)有关;

2.构成:

dp【n】=cost:价值为n时的最优解;

下标:组成的价值n,值:需硬币个数为cost;

dp【n-1】+1:价值n-1时的最优解+一个价值为1的硬币   (总价值为n)

dp【n-5】+1:价值n-5时的最优解+一个价值为5的硬币    (总价值为n)

dp【n-11】+1:价值n-11时的最优解+一个价值为11的硬币 (总价值为n)

3.伪代码:min,max找最优解+分类讨论

 if(i-1>=0) cost=min(cost,dp[i-1]+1);     
 if(i-5>=0) cost=min(cost,dp[i-5]+1);
 if(i-11>=0) cost=min(cost,dp[i-11]+1);

  

int min(int a,int b)
{
    return a<b?a:b;
}
int main()
{ 
     int dp[100],i;                  //dp【n】价值为n时的最优解硬币个数
     int cost;                      //花费硬币数量
     
    dp[0]=0;                       //边界
    for(i=1; i<=15; i++)           //满15元(价值15)
    {
        cost=99999999;                       //每次重置cost
        if(i-1>=0) cost=min(cost,dp[i-1]+1);     
        if(i-5>=0) cost=min(cost,dp[i-5]+1);
        if(i-11>=0) cost=min(cost,dp[i-11]+1);  //dp【n】+1代表价值n的最优解+价值1

        dp[i]=cost;                //硬币个数
        printf("%d",dp[i]);
    }
}

01背包 


每件物品仅一件01

问题描述:

假设背包的容量是 C=10,求一定容量内能装物品价值最大为?

物品编号:1 2 3
物品重量:5 6 4
物品价值:20 10 12


1.含义:

v【i】:第 i 件物品价值;

w【i】:第 i 件物品重量;

dp【i】【j】:可装前 i 件物品时,所能承受重量 j ,最大总价值

2.底层条件:dp【0】【j】=0;

3.递推公式:拿or不拿?dp[i][j]=max( (dp[i-1][ j−w[i] ] ) + v[i] , dp[i-1][j])

拿:dp【i】【j】=dp【i-1】【j-w【i】】+v【i】

不拿:dp【i】【j】=dp【i-1】【j】

4.注意:容量逆序,for(int j=t ; j>=0 ; j--)      

在这里插入图片描述

#include <iostream>

using namespace std;
int w[105],v[105];
int dp[105][1005];
int main()
{

    int t,m,res;
    scanf("%d%d",&t,&m);

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&w[i],&v[i]);
    }

    for(int i=1;i<=m;i++)
    {
        for(int j=t;j>=0;j--)      //容量逆序
        {
            if(j>=w[i])              //所剩容量大于需装物体体积
            {
                dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]); //装or不装
            }

           else                   //只能不装
            {
              dp[i][j]=dp[i-1][j];
            }
        }
    }

    printf("%d",dp[m][t]);
    return 0;
}

滚动数组优化


定义 数组内数据滚动起来(实时更新),二维dp简化为一维dp

原理:由于每次更新dp【i】【】数组,只需知道dp【i-1】【】信息,故可利用一维滚动dp数组优化

注意:反向枚举体积,逆序

如若正序更新滚动数组,则会出现dp【i】【】使用更新后的dp【i-1】【】,导致结果出错

​
for(int i=0;i<=v;i++)
{
    dp[i]=0;             //边界处理
}
for(int i=1;i<=N;i++)
{
    for(int j=V;j>=w[i];j--)    //逆序!!!
    {
        dp[j]=max(v[i]+dp[j-w[i]],dp[j]);
    }
}
//放得下:v【i】+dp【j-w[i]】
//放不下:dp【j】

​

刷题 

 1.金字塔最大价值路径

P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1216

1.题意:顶部到底部最大价值的路径,求最大价值和

2.构成:a【】【】存图金字塔价值,dp【】【】当前价值,maxx中最大价值和

3.输入:输入金字塔,存入a【】【】数组

for(int i=1; i<=n ;i++)

for(int j=1; j<=i ;j++)

4.底层条件:初始化边界值(终止条件)

5.递推式:// 上一点A向右走或向下走到下一点B

dp[i][j]=max( dp[i-1][j] , dp[i-1][j-1] )+a[i][j]

6.更新最大值maxx:尝试不同路,保留每步step最优解

7.注意:价值求和加到尾巴上

#include<stdio.h>
int max(int a,int b) 
{
	return a>b?a:b;
}
int main()
{
	int a[1001][1001],dp[1001][1001];
	int n; 
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=i;j++)          //金字塔输入 
	{
		scanf("%d",&a[i][j]);
	}
	
	int maxx=0;
	dp[1][1]=a[1][1];                 //边界值 
	 
	for(int i=2;i<=n;i++)            //从2开始,1为起点
	for(int j=1;j<=n;j++)
	{
		dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j];   //状态转移方程 + 求最大价值的和 
		maxx=max(maxx,dp[i][j]);           //更新最大价值 
	 } 
	 
	 printf("%d\n",maxx);
	 return 0;
}

2. 过河卒

P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1002

1.题意:二维地图,跳过马从起点到终点的路径条数;

2.关键:加套两层边界

3.构成:

bx,by:终点B

hx,hy:马王点

horse【】【】:存马的位置

f【i】【j】:走到终点(i,j)的最优解

4.初始化边界:f[1][2]=1;初始最初的最初(含加套的两层)   

5.注意:

马的分布规律(四周日字),考虑马位于边界情况,故加套两层边界

起点f【2】【2】不可直接设为1

数据类型:long long

#include<stdio.h>
int main()
{
	long long horse[30][30]= {0},bx,by,hx,hy, f[30][30]; //long long
	scanf("%lld%lld%lld%lld",&bx,&by,&hx,&hy);
	
	bx+=2;
	by+=2;
	hx+=2;
	hy+=2;                     //对边界加两层边套(马的特性)

	for(int i=2; i<30; i++)    //边界赋值
	{
		f[2][i]=1;        //一直向右直走,情况为1
		f[i][2]=1;        //一直向下直走,情况为1
	}

	horse[hx][hy]=1;         //标记马的点,不能走 
	horse[hx-2][hy-1]=1;
	horse[hx-1][hy-2]=1;
	horse[hx+1][hy+2]=1;
	horse[hx+2][hy+1]=1;

	horse[hx-1][hy+2]=1;
	horse[hx+1][hy-2]=1;
	horse[hx-2][hy+1]=1;
	horse[hx+2][hy-1]=1;
	
	
	f[1][2]=1;               //到达起点前,可能是(1,2)或(2,1)到达(2,2),故步数设为1                
    for(int i=2;i<=bx;i++)      //实际地图从(2,2)开始 
	for(int j=2;j<=by;j++)
	{
		if(horse[i][j]==1)      //遇到障碍物马 
		{
			f[i][j]=0;         //归零
			continue;
		}
		
		f[i][j]=f[i-1][j]+f[i][j-1]; //递推式
	 } 
	 
	 printf("%lld",f[bx][by]); 
}

 3.疯狂的采药

P1616 疯狂的采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1616
1.关键:完全背包问题,无限数量;

2.正序:

for(i=1;i<=m;i++)
       
for(j=c[i];j<=t;j++)             
            dp[j]=max(dp[j],dp[j-c[i]]+w[i]);

3.注意:数据类型 long long

#include <stdio.h>
long long c[20000000],w[20000000],dp[20000000];
long long max(long long a,long long b)
{
	return a>b?a:b;
}
int main()
{
    long long t,m,i,j;
    scanf("%lld%lld",&t,&m);
    
    for(i=1;i<=m;i++)
        scanf("%lld%lld",&c[i],&w[i]);         //c【】耗时,w【】价值
        
    for(i=1;i<=m;i++)
        for(j=c[i];j<=t;j++)                //正序
            dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
            
    printf("%lld",dp[t]);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值