背包问题

背包问题

P1048 [NOIP2005 普及组] 采药

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是辰辰,你能完成这个任务吗?

输入格式

第一行有 2 个整数 T(1≤T≤1000)和 M(1≤M≤100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。

接下来的 M 行每行包括两个在 1 到 100之间(包括 1 和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出格式

输出在规定的时间内可以采到的草药的最大总价值。

输入输出样例

输入 #1

1
2
3
4
70 3
71 100
69 1
1 2

输出 #1

1
3

说明/提示

【数据范围】

  • 对于 30% 的数据 M≤10;
  • 对于全部的数据 M≤100。

思路部分

想了一下dfs至少能a掉30%的数据,但这不是我们的本意,本意当然是想到dp的做法

抽象一下题意,我们要做的是求在n个草药中选择,使容量为W的背包获得的价值最大

因此我们设dp[i] [j]表示容量为j的选择前i个草药能获得的最大价值,以此可以表示最终答案的状态

要求出最终答案,我们要解决最终答案的子问题

\[ dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) \]

优化空间复杂度

我们发现,我们在状态转移时并没有利用i的,换言之,要求dp[i] [j],我们不关心前i个草药怎么选,我们只要知道容量小的背包的最优解即可完成状态的转移。因此可以只设dp[j],表示容量为j的背包的最优价值。

思考一下,为什么要从后往前遍历

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <bits/stdc++.h>
using namespace std;
long long dp[10000010];
int w[10010],v[10010];
int main() {
int n,t;
scanf("%d%d",&t,&n);
for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&v[i]) ;
for(int i=1; i<=n;i++) {
for(int j=t; j>=w[i]; j--) {
dp[j]=max(dp[j],dp[j-w[i]]+1ll*v[i]);
}
}
printf("%lld",dp[t]);
return 0;
}

P1616 疯狂的采药

题目描述

LiYuxiang 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同种类的草药,采每一种都需要一些时间,每一种也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

如果你是 LiYuxiang,你能完成这个任务吗?

此题和原题的不同点:

  1. 每种草药可以无限制地疯狂采摘。

  2. 药的种类眼花缭乱,采药时间好长好长啊!师傅等得菊花都谢了!

输入格式

输入第一行有两个整数,分别代表总共能够用来采药的时间 t 和代表山洞里的草药的数目 m

第 2 到第(m+1) 行,每行两个整数,第(i+1) 行的整数 ai, bi 分别表示采摘第 i 种草药的时间和该草药的价值。

输出格式

输出一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

输入输出样例

输入 #1复制

1
2
3
4
70 3
71 100
69 1
1 2

输出 #1

1
140

说明/提示

数据规模与约定

  • 对于 30% 的数据,保证m≤103

  • 对于 100% 的数据,保证 1≤m≤104,1≤t≤107,且 1≤m×t≤107,1≤ai,bi≤104

    转化为等价于价值为kv[i],花费为k****w[i]的物品

    \[ dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i])0≤k∗c[i]≤j \]

    时间优化

    状态更新的方式决定了物品的数量,从前往后更新,可以更新到已选本身的状态,于是达到无限选择物品目的

    代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
using namespace std;
long long dp[10000010];
int w[10010],v[10010];
int main() {
int n,t;
scanf("%d%d",&t,&n);
for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&v[i]) ;
for(int i=1; i<=n;i++) {
for(int j=w[i]; j<=t; j++) {
dp[j]=max(dp[j],dp[j-w[i]]+1ll*v[i]);
}
}
/*for(int i=1;i<=n;i++){
for(int j=W;j>=w[i];j--){
for(int k=1;k<m[i];k++){
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]);
}
}
}*/
printf("%lld",dp[t]);
return 0;
}
思考一下,为什么又变成了从前往后更新

能用动规解决的问题的特点

能采用动态规划求解的问题的一般要具有3个性质:

1
2
3
4
(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。

(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

动规解题的一般思路

动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤。

1
2
3
4
5
6
7
8
9
10
11
初始状态→│决策1│→│决策2│→…→│决策n│→结束状态

1 动态规划决策过程示意图

(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。

(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。

(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出。但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。

(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。

算法实现的说明

动态规划的主要难点在于理论上的设计,也就是上面4个步骤的确定,一旦设计完成,实现部分就会非常简单。

使用动态规划求解问题,最重要的就是确定动态规划三要素:

(1)问题的阶段 (2)每个阶段的状态

(3)从前一个阶段转化到后一个阶段之间的递推关系。


版权声明:本文为CSDN博主「zw6161080123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/zw6161080123/article/details/80639932


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!