这里看:昨天说过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