动态规划01背包问题(2)

这里看:昨天说过150结果这今天一看666.那么好话不多说开干!

书接上文————

开心的小明 [happy]

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:
v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号)
请你帮助金明设计一个满足要求的购物单。

输入格式

第1行,为两个正整数,用一个空格隔开:
N m
(其中N(<30000)表示总钱数,m(<25)为希望购买物品的个数。)
从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有2个非负整数
v p
(其中v表示该物品的价格(v≤10000),p表示该物品的重要度(1~5))

输出格式

输出文件happy.out只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。

样例

输入数据#1

1000 5
800 2
400 5
300 5
400 3
200 2

输出数据#1

3900 

[算法分析]小明拥有的钱是背包大小,物品的价格是其占用的背包容量,物品的价格*重要度是其带来的价值,题目要求价值最大。定义状态∶ d(i,j)表示为前i个物品分配j容量的背包,可以获得的最大价值。对于物品i有2种选择∶选择1∶ 不选择将i放入背包∶ d(i,j)= d(i-1, j)。选择2∶选择将i放入背包d(i,j)=d(i-1,j-wi​)+vi​(j>=wi​)。状态转移方程∶ d(i, j)= max(d(i-1,j), d(i-1,j-wi​)+vi​)。注意∶物品价值是他的价格和重要度的乘积! 

完整的代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=30,M=30005;
int n,m,w[N],v[N],d[N][M],t;
int main()
{ 
    //n件物品,承重量为c 
    cin>>m>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i]>>t; //t表示重要度 
        v[i]=t*w[i]; //价值v[i]是重要度乘以价格 
    }
    for(int i=1;i<=n;i++)  
    {
        for(int j=1;j<=m;j++)  
        {
            if(j>=w[i])
                d[i][j]=max(d[i-1][j],d[i-1][j-w[i]]+v[i]);
            else
                d[i][j]=d[i-1][j];
        }
    }
    cout<<d[n][m];
    return 0;
} 

装箱问题

题目描述

有一个箱子容量为v(正整数,0≤v≤20000),同时有n个物品(0≤n≤30),每个物品有一个体积 (正整数)。要求从 n 个物品中,任取若千个装入箱内,使箱子的剩余空间为最小。

输入格式

第一行,一个整数,表示箱子容量;
第二行,一个整数,表示有n个物品;
接下来n行,分别表示这n个物品的各自体积。

输出格式

一个整数,表示箱子剩余空间。

样例

输入数据#1

24 
6 
8 
3 
12 
7 
9 
7 

输出数据#1

0

动态规划空间优化之滚动数组 

动态规划本就是一个记录再利用的高效算法,由于其要记录之前的状态,必然会使用大量空间,要优化动态规划算法的空间,我们必然要合理利用dp数组,有一种优化方法就是利用滚动数组来进行状态转移。我们拿动态规划中一个典型例子01背包来举例

问题描述:

有N件物品和一个容量为c的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。当我们不进行优化的时候,我们可以定义如下所示的最优子结构dp[i][j] 表示把前i件放入容量为j的背包中的最大价值转移过程如下:

for(int i = 1; i <= n; i++)
{
  for(int j = 1; j <= c; 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]);
  }
}
ans = dp[n][c];

要优化空间复杂度,我们要理解我们dp数组里存的是什么:dp数组第一维表示的是考虑前几个物品,其实我们就是按照这一维来划分动态规划过程的阶段

我们一定要理解,动态规划的过程是分阶段进行的,每一个阶段都有很多不同的状态,而对于某一阶段的某一状态,往往需要从不同阶段的不同状态转移过来。

比如这个问题中,第一阶段就是考虑第一个物品放入背包,第二阶段就是考虑前两个物品放入背包,然后以此类推,直到最后一阶段转移完成,我们才能得到最后的答案。所以我们的dp数组其实存下了每一个阶段的所有状态。但是我们仔细观察我们的状态转移方程:

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]);

对于每个阶段的状态,我们发现它们都是从上一阶段转移过来的,所以我们完全没必要存下所有阶段的状态,我们只要保存上一阶段的所有状态和当前阶段的所有状态就行了,再利用这两个数组不断滚动计算,就可以得到我们想要的最后一阶段的最后一个状态dp[n] [c],即题目要求的最优解的状态。

其实我们还可以进一步优化,我们可以注意到比如第i个阶段第j个状态它是从第i-1个阶段第j个状态前面转移过来的,所以我们完全只用一个一维的dp数组就够了,即我们只需要保存上一个阶段的所有状态,当前阶段的状态可以从这个数组中转移并储存,而不影响别的状态的转移(比如我们计算得到了当前阶段的第j个状态的值,我们覆盖掉保存的上一个阶段第j个状态的值,这不会影响后面状态的转移,因为后面的状态不会再用到这个值了)

但是我们要注意一点,前面已经说了,当前阶段的状态是从上一个阶段转移过来的,如果我们用一维dp数组,我们还是按顺序转移,即如下所示的转移过程:

for(int i = 1; i <= n; i++)
{
  for(int j = w[i]; j <= c; j++)
  {
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
  }
}

反过来,对于第j个状态,只会从c到w[i]的状态转移过来,即不会从后面转移过来,就不会发生之前的错误了。让我们回过头来再看看我们之前做的01背包的题目:

01背包问题

[算法分析]

根据上文滚动数组的介绍,我们可以对之前的代码进行优化,优化后的代码如下所示:

 

#include<bits/stdc++.h>
using namespace std;
/*
一维数组的优化:
状态转移方程:dp[j]=max(dp[j],vi+dp[j-w[i]];
*/
int c;//背包容量
int dp[210];//dp[j]表示重量不超过j公斤的最大价值
int n,wi,vi; 
int main(){
    cin >> c >> n;
    for(int i=1;i<=n;i++){
        cin>> wi>> vi;
        //逆序存储,直到背包容量小于此时物品的重量
        //讨论每个物品选和不选的两个状态时,得到的最大的价值度 
        for(int j=c;j>=wi;j--){ 
            dp[j]=max(dp[j],dp[j-wi]+vi);
        }
    }
    cout << dp[c];
}

制作手链

母亲节快到了,小拓想着要送给妈妈一件特别的礼物,他想亲手制作一条手链送给他的妈妈。所以小拓来到珠宝店,他想用N(1≤N≤3500)个链珠制作成一条最好的手链。每个链珠i都有重量wi​(1≤wi​≤400)和价值Di(1≤Di≤100),一个链珠最多可使用一次。但是手链不能太重,太重了妈妈戴起来会不舒服,所以他决定手链的总重量不能超过M(1≤M≤13000)。给定重量总重量限制M以及N个链珠的重量和价值,请帮助小拓计算出该手链最大可能的价值总和。输入格式第一行:空格分隔的两个整数N、M,N表示链珠个数、M表示总重量限制第二行至第N+1行:第i个物品的重量W[i]和价值D[i]输出格式输出一行表示手链的最大价值。输入输出样列

输入样例1:
 

4 6
1 4
2 6
3 12
2 7

输出样例1:

23

[算法分析]

将手链的最大重量作为背包大小,链珠作为物品,物品占用背包,同时带来价值。定义状态∶ d(i,j)表示为前i个链珠分配j的重量,可以获得的最大价值。对链珠i有2种选择∶决策1∶ 不选择将i放入背包∶d(i,j ) = d(i-1, j)。决策2∶选择将i放入背包∶d(i,j)=d(i-1,j-wi​)+vi​; (j>=wi​)。状态转移方程∶ d(i,j)= max(d(i-1,j),d(i-1,j-wi​)+vi​)。滚动数组∶ d(j) = max(d(j),d(j-wi​)+vi​)。

#include<bits/stdc++.h>
using namespace std;
const int N=3505,C=13005;
int n,w[N],v[N],d[C],c;
int main()
{  
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    {
        cin>>w[i]>>v[i];
    }
    for(int i=1;i<=n;i++)  
    {
        for(int j=c;j>=w[i];j--)  //枚举背包,必须从大到小枚举  
        {
            d[j]=max(d[j],d[j-w[i]]+v[i]);
        }
    }
    cout<<d[c];
    return 0;
} 

选数字

卡卡西博士在跟小拓一起玩选数字的游戏,卡卡西博士给出了N个正整数,让小拓请从中选择一些数字,要求这些数字的和为T,请帮助小拓计算一下一共有多少种不同的数字组合方式?

输入格式

输入的第一行是两个正整数,分别表示N和T(1≤N≤20,1≤T≤1000)接下来一行,是N个正整数输出格式输出一行,表示不同的组合方式的数量输入输出样列

输入样例1:

5 5
1 2 3 4 5

输出样例1:

3

说明样例说明:

N=5,5个数字分别是1,2,3,4,5T=5,那么可能的组合方式有:5=1+4、5=2+3、5=5,这三种组合方式,所以输出为3

[算法分析]

将希望凑出的数字和T作为背包大小,将数字作为物品,问题可以转换为01背包方案数问题。用N个物品(数字)将大小为T的背包装满的方案数。定义状态∶ d(i, j)表示为前i个数字凑出整数j的方案数,最终答案∶ d[N][M]。对整数i有2种选择∶决策1∶不选择数字i∶ d(i,j)= d(i-1, j)(用前i-1个数字凑j)。决策2∶选择数字i∶ d(i,j)= d(i-1,j-wi​)(j>=wi​)(用前i-1个数字凑j-w[i])。状态转移方程∶ d(i, j) = d(i-1, j)+d(i-1, j-wi​)。滚动数组∶ d(j)+= d(j-wi​),初始化∶ d[0] = 1。

#include<bits/stdc++.h>
using namespace std;
const int N=25,T=1005;
int n,t,w[N],d[T]; 
int main()
{
    cin>>n>>t;  
    for(int i=1;i<=n;i++) 
    {
        cin>>w[i];
    }
    d[0]=1;  //初始化
    for(int i=1;i<=n;i++)
    {
        for(int j=t;j>=w[i];j--)  //滚动数组从大到小枚举j 
        {
            d[j]=d[j]+d[j-w[i]];
        }
    }
    cout<<d[t];
    return 0;
     

}

好了,这动态规划01背包问题已经完结了;

下一期,播放量到170马上更新下一期,下一期不见不散~

~~~~~

大家一起

 

see yuo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值