通过金矿模型介绍动态规划(介绍了01背包)
1/*
2=========程序信息========
3对应题目:noip2006提高组_金明的预算方案
4使用语言:c++
5使用编译器:dev c++
6使用算法:动态规划
7算法运行时间:O( n * m ) [n是钱数,m是物品数,O(n*m)是最坏情况,实际值小于O(n*m)]
8作者:贵州大学05级 刘永辉
9昵称:SDJL
10编写时间:2008年8月
11联系QQ:44561907
12E-Mail:44561907@qq.com
13获得更多文章请访问我的博客:www.cnblogs.com/sdjl
14如果发现BUG或有写得不好的地方请发邮件告诉我:)
15转载请保留此信息:)
16=========题目解析========
17此题是01背包问题的变形,如果读者对01背包问题还不熟悉的话请看先我的另一篇关于01背包问题的文章。
18下面我将假设你已经了解了01背包问题。
19
20题目补充点一:为了让程序容易看懂些,我们忽略题目中的价格都是10的这个条件,其实这个条件可以减少一些时间和空间的开销。
21
22我们把此题看成背包问题,物品的重要度乘以价格是背包问题中的价值,以后说道价值就是指这两个值的乘积,物品的价格是背包问题中的体积,同样以后说道体积就是说物品的价格。
23
24我们可以通过以下几个关键从而把问题看得简单一些:
251、既然买附件时一定要买主件,那么可以认为每个主件有4种购买情况(分别是只买主件,买主件和附件1,买主件和附件2,买主件和两个附件), 而不用单独考虑附件是否购买的情况。
26这样我们就把如何在众多主件与附件之中选择购买的问题转变为如何在主件中选择购买的问题了。对于每个主件,其实还有不买的情况,我们把这种情况和上面4种看成购买的5种方案(方案0是只买主件,方案1是买主件和附件1,方案2是买主件和附件2,方案3是买主件和两个附件,方案4是什么都不买)。
272、有些主件有附件,而有些没有,这为我们思考带来了负担,我们完全可以假设任何主件都有两个附件,也就是说如果题目没有给出某个主件的附件的话,我们就假设这个主件的附件是存在的,且价格和重要度都等于0(对应背包问题就是价值和体积都等于0)。
28这个假设首先不会影响到程序的正确性,也不会增加多少运算时间,且这种假设使得我们想问题和写程序都变得简单多了。
29
30考虑到这里读者发现此题和01背包问题有什么区别了吗?唯一的区别主要有两点:
31区别一:01背包问题对当前物品考虑的只有买和不买两种情况,而此题需要考虑上面所说的5种不同的购买方案。
32区别二:01背包问题是用v[i]来保存第i个物品的价值,而此题需要用v[i]来保存第i个物品和它的两个附件的价值,此时我们需要二维数组来实现,物品体积w同样需要用二维数组来保存,请看全局变量的注释。
33
34下面是对应的程序代码:
35
36*/
37
38#include <cstdlib>
39#include <iostream>
40#include <fstream>
41
42using namespace std;
43
44const int maxm=60;//程序支持的最大物品数
45const int maxn=32000;//程序支持的最大体积
46
47int n;//钱数
48int m;//物品数(也就是主件的个数)
49int v[maxm][3];//v[i][0]表示第i个物品的主件价值, v[i][1]表示第i个物品的第一个附件的价值,v[i][2]表示第i个物品的第二个附件的价值
50int w[maxm][3];//与v差不多,不过w表示的是体积
51int maxValue[maxn][maxm];//maxValue[i][j]保存了给定i个空间和前j个物品(说道物品,均包含主件和两个附件,下同)能够获得的最大价值总合。
52
53//初始化数据
54void init()
55{
56 //这里我们用v[i][1]=-1 表示第i个物品的第1个附件还不存在
57 for(int i=0;i<maxm;i++)
58 v[i][1]=-1;
59
60 ifstream fin("budget10.in");
61 //获得钱数与物品个数,注意,这里暂时用m保存主件与附件的总数,而不是主件数
62 fin>>n>>m;
63
64 int o,p,q;
65 /*
66 根据题目的意思,q是物品的编号,但是这个编号是在考虑附件时统计的编号,而我们认为附件和主件是一体的,因此附件编号因该和主件一致,所以我们对题目给出的编号进行转换。
67 下面我们把题目给出的编号称之为假编号,把物品所对应的主件(主件对应的主件就是自己)编号称之为真编号。
68 因此当我们遇到附件的时候,我们需要知道这个附件的真编号是多少,而这个真编号就保存在mainItemNum数组中。
69 而当我们遇到的第i个物品是主件时,那么同样的mainItemNum[i]表示这个主件的真编号。
70 */
71 int mainItemNum[maxm];
72 int mainItemCount = 0;//我们用mainItemCount表示有多少个主件
73 //依次读取物品
74 for(int i=0;i<m;i++)
75 {
76 fin>>o>>p>>q;
77 //如果此物品是主件
78 if(q == 0)
79 {
80 //获得主件价值
81 v[mainItemCount][0] = o * p;
82 //获得主件体积
83 w[mainItemCount][0] = o;
84 //记下这个物品的真编号。注意,i是假编号
85 mainItemNum[i] = mainItemCount;
86 mainItemCount++;
87 }
88 else //否则这个物品是附件
89 {
90 //获得这个物品的真编号。注意,程序是从0开始编号,而题目是从1开始,所以要减去1
91 int mainOrder = mainItemNum[q - 1];
92 //如果这个主件的附件1不存在
93 if(v[mainOrder][1] == -1)
94 {
95 //设置为附件1
96 v[mainOrder][1] = o * p;
97 w[mainOrder][1] = o;
98 }
99 else//否则这个主件的附件1存在
100 {
101 //设置为附件2
102 v[mainOrder][2] = o *p;
103 w[mainOrder][2] = o;
104 }
105 }
106 }
107 fin.close();
108 //就像全局申明时所说的那样,让m表示主件的个数
109 m=mainItemCount;
110
111 //初始化maxValue为-1,表示这个值未知
112 for(int i=0;i<maxn;i++)
113 for(int j=0;j<maxm;j++)
114 maxValue[i][j]=-1;
115
116 /*
117 刚才我们为了判断主件是否拥有附件1时设置了v[i][1]=-1,当时这与我们的“附件不存在时认为其价值为0”矛盾,所以现在要设置回来。
118 */
119 for(int i=0;i<maxm;i++)
120 if(v[i][1] == -1)
121 v[i][1]=0;
122}
123
124
125
126//获得实施scheme方案购买第itemNum个物品至少需要的钱数
127int GetNeededMoney(int itemNum,int scheme)
128{
129 //申明区要的钱数
130 int retMoney = 0;
131 //考虑对应的5种方案
132 switch(scheme)
133 {
134 case 0:
135 retMoney=w[itemNum][0];
136 break;
137 case 1:
138 retMoney=w[itemNum][0]+w[itemNum][1];
139 break;
140 case 2:
141 retMoney=w[itemNum][0]+w[itemNum][2];
142 break;
143 case 3:
144 retMoney=w[itemNum][0]+w[itemNum][2]+w[itemNum][1];
145 break;
146 case 4:
147 retMoney=0;
148 break;
149 }
150 return retMoney;
151}
152
153
154
155//获得在只有money个钱,对第itemNum个物品实施第scheme种购买方案时能够得到的最大价值
156int GetSchemeMoney(int money,int itemNum,int scheme)
157{
158 //申明这个最大价值
159 int retValue = 0;
160 //分别考虑5种情况
161 switch(scheme)
162 {
163 case 0:
164 if(money >= GetNeededMoney(itemNum,scheme))
165 retValue = v[itemNum][0];
166 break;
167 case 1:
168 if(money >= GetNeededMoney(itemNum,scheme))
169 retValue = v[itemNum][0]+v[itemNum][1];
170 break;
171 case 2:
172 if(money >= GetNeededMoney(itemNum,scheme))
173 retValue = v[itemNum][0]+v[itemNum][2];
174 break;
175 case 3:
176 if(money >= GetNeededMoney(itemNum,scheme))
177 retValue = v[itemNum][0]+v[itemNum][1]+v[itemNum][2];
178 break;
179 case 4:
180 retValue = 0;
181 break;
182 }
183 return retValue;
184}
185
186
187
188//获得剩余的residualMoney钱(体积)和前itemNum个物品时能够得到的最大价值
189int MaxValue(int residualMoney,int itemNum)
190{
191 //申明返回的最大价值
192 int bestValue = 0;
193
194 //如果这个价值已经算出来了 [这里对因动态规划的“做备忘录”]
195 if(maxValue[residualMoney][itemNum]!=-1)
196 {
197 bestValue = maxValue[residualMoney][itemNum];
198 }
199 else if(itemNum==0) //否则如果这个物品是最后一个 [这里对应动态规划的“边界”]
200 {
201 //用仅有的residualMoney个钱考虑5种购买方案,并把获得的最大价值放在bestValue中
202 for(int i=0; i<5; i++)
203 {
204 bestValue = max(bestValue,GetSchemeMoney(residualMoney,itemNum,i));
205 }
206
207 }
208 else//否则不是最后一个物品 [这里对应动态规划的“最优子结构”]
209 {
210 //考虑此物品的5种购买方案
211 for(int i=0; i<5; i++)
212 {
213 int useMoney = GetNeededMoney(itemNum,i);
214 if(residualMoney >= useMoney)
215 {
216 bestValue = max(bestValue, GetSchemeMoney(useMoney,itemNum,i) + MaxValue(residualMoney - useMoney,itemNum-1) );
217 }
218 }
219 }
220
221 maxValue[residualMoney][itemNum] = bestValue;
222 return bestValue;;
223}
224
225
226
227
228int main(int argc, char *argv[])
229{
230 //初始化数据
231 init();
232 //输出在仅有n个钱(体积)时,对于前m-1个物品能够得到的最大价值,注意是从0开始编号,所以m-1是最后一个物品
233 cout<<MaxValue(n,m-1);
234
235 system("PAUSE");
236 return EXIT_SUCCESS;
237}
238
2=========程序信息========
3对应题目:noip2006提高组_金明的预算方案
4使用语言:c++
5使用编译器:dev c++
6使用算法:动态规划
7算法运行时间:O( n * m ) [n是钱数,m是物品数,O(n*m)是最坏情况,实际值小于O(n*m)]
8作者:贵州大学05级 刘永辉
9昵称:SDJL
10编写时间:2008年8月
11联系QQ:44561907
12E-Mail:44561907@qq.com
13获得更多文章请访问我的博客:www.cnblogs.com/sdjl
14如果发现BUG或有写得不好的地方请发邮件告诉我:)
15转载请保留此信息:)
16=========题目解析========
17此题是01背包问题的变形,如果读者对01背包问题还不熟悉的话请看先我的另一篇关于01背包问题的文章。
18下面我将假设你已经了解了01背包问题。
19
20题目补充点一:为了让程序容易看懂些,我们忽略题目中的价格都是10的这个条件,其实这个条件可以减少一些时间和空间的开销。
21
22我们把此题看成背包问题,物品的重要度乘以价格是背包问题中的价值,以后说道价值就是指这两个值的乘积,物品的价格是背包问题中的体积,同样以后说道体积就是说物品的价格。
23
24我们可以通过以下几个关键从而把问题看得简单一些:
251、既然买附件时一定要买主件,那么可以认为每个主件有4种购买情况(分别是只买主件,买主件和附件1,买主件和附件2,买主件和两个附件), 而不用单独考虑附件是否购买的情况。
26这样我们就把如何在众多主件与附件之中选择购买的问题转变为如何在主件中选择购买的问题了。对于每个主件,其实还有不买的情况,我们把这种情况和上面4种看成购买的5种方案(方案0是只买主件,方案1是买主件和附件1,方案2是买主件和附件2,方案3是买主件和两个附件,方案4是什么都不买)。
272、有些主件有附件,而有些没有,这为我们思考带来了负担,我们完全可以假设任何主件都有两个附件,也就是说如果题目没有给出某个主件的附件的话,我们就假设这个主件的附件是存在的,且价格和重要度都等于0(对应背包问题就是价值和体积都等于0)。
28这个假设首先不会影响到程序的正确性,也不会增加多少运算时间,且这种假设使得我们想问题和写程序都变得简单多了。
29
30考虑到这里读者发现此题和01背包问题有什么区别了吗?唯一的区别主要有两点:
31区别一:01背包问题对当前物品考虑的只有买和不买两种情况,而此题需要考虑上面所说的5种不同的购买方案。
32区别二:01背包问题是用v[i]来保存第i个物品的价值,而此题需要用v[i]来保存第i个物品和它的两个附件的价值,此时我们需要二维数组来实现,物品体积w同样需要用二维数组来保存,请看全局变量的注释。
33
34下面是对应的程序代码:
35
36*/
37
38#include <cstdlib>
39#include <iostream>
40#include <fstream>
41
42using namespace std;
43
44const int maxm=60;//程序支持的最大物品数
45const int maxn=32000;//程序支持的最大体积
46
47int n;//钱数
48int m;//物品数(也就是主件的个数)
49int v[maxm][3];//v[i][0]表示第i个物品的主件价值, v[i][1]表示第i个物品的第一个附件的价值,v[i][2]表示第i个物品的第二个附件的价值
50int w[maxm][3];//与v差不多,不过w表示的是体积
51int maxValue[maxn][maxm];//maxValue[i][j]保存了给定i个空间和前j个物品(说道物品,均包含主件和两个附件,下同)能够获得的最大价值总合。
52
53//初始化数据
54void init()
55{
56 //这里我们用v[i][1]=-1 表示第i个物品的第1个附件还不存在
57 for(int i=0;i<maxm;i++)
58 v[i][1]=-1;
59
60 ifstream fin("budget10.in");
61 //获得钱数与物品个数,注意,这里暂时用m保存主件与附件的总数,而不是主件数
62 fin>>n>>m;
63
64 int o,p,q;
65 /*
66 根据题目的意思,q是物品的编号,但是这个编号是在考虑附件时统计的编号,而我们认为附件和主件是一体的,因此附件编号因该和主件一致,所以我们对题目给出的编号进行转换。
67 下面我们把题目给出的编号称之为假编号,把物品所对应的主件(主件对应的主件就是自己)编号称之为真编号。
68 因此当我们遇到附件的时候,我们需要知道这个附件的真编号是多少,而这个真编号就保存在mainItemNum数组中。
69 而当我们遇到的第i个物品是主件时,那么同样的mainItemNum[i]表示这个主件的真编号。
70 */
71 int mainItemNum[maxm];
72 int mainItemCount = 0;//我们用mainItemCount表示有多少个主件
73 //依次读取物品
74 for(int i=0;i<m;i++)
75 {
76 fin>>o>>p>>q;
77 //如果此物品是主件
78 if(q == 0)
79 {
80 //获得主件价值
81 v[mainItemCount][0] = o * p;
82 //获得主件体积
83 w[mainItemCount][0] = o;
84 //记下这个物品的真编号。注意,i是假编号
85 mainItemNum[i] = mainItemCount;
86 mainItemCount++;
87 }
88 else //否则这个物品是附件
89 {
90 //获得这个物品的真编号。注意,程序是从0开始编号,而题目是从1开始,所以要减去1
91 int mainOrder = mainItemNum[q - 1];
92 //如果这个主件的附件1不存在
93 if(v[mainOrder][1] == -1)
94 {
95 //设置为附件1
96 v[mainOrder][1] = o * p;
97 w[mainOrder][1] = o;
98 }
99 else//否则这个主件的附件1存在
100 {
101 //设置为附件2
102 v[mainOrder][2] = o *p;
103 w[mainOrder][2] = o;
104 }
105 }
106 }
107 fin.close();
108 //就像全局申明时所说的那样,让m表示主件的个数
109 m=mainItemCount;
110
111 //初始化maxValue为-1,表示这个值未知
112 for(int i=0;i<maxn;i++)
113 for(int j=0;j<maxm;j++)
114 maxValue[i][j]=-1;
115
116 /*
117 刚才我们为了判断主件是否拥有附件1时设置了v[i][1]=-1,当时这与我们的“附件不存在时认为其价值为0”矛盾,所以现在要设置回来。
118 */
119 for(int i=0;i<maxm;i++)
120 if(v[i][1] == -1)
121 v[i][1]=0;
122}
123
124
125
126//获得实施scheme方案购买第itemNum个物品至少需要的钱数
127int GetNeededMoney(int itemNum,int scheme)
128{
129 //申明区要的钱数
130 int retMoney = 0;
131 //考虑对应的5种方案
132 switch(scheme)
133 {
134 case 0:
135 retMoney=w[itemNum][0];
136 break;
137 case 1:
138 retMoney=w[itemNum][0]+w[itemNum][1];
139 break;
140 case 2:
141 retMoney=w[itemNum][0]+w[itemNum][2];
142 break;
143 case 3:
144 retMoney=w[itemNum][0]+w[itemNum][2]+w[itemNum][1];
145 break;
146 case 4:
147 retMoney=0;
148 break;
149 }
150 return retMoney;
151}
152
153
154
155//获得在只有money个钱,对第itemNum个物品实施第scheme种购买方案时能够得到的最大价值
156int GetSchemeMoney(int money,int itemNum,int scheme)
157{
158 //申明这个最大价值
159 int retValue = 0;
160 //分别考虑5种情况
161 switch(scheme)
162 {
163 case 0:
164 if(money >= GetNeededMoney(itemNum,scheme))
165 retValue = v[itemNum][0];
166 break;
167 case 1:
168 if(money >= GetNeededMoney(itemNum,scheme))
169 retValue = v[itemNum][0]+v[itemNum][1];
170 break;
171 case 2:
172 if(money >= GetNeededMoney(itemNum,scheme))
173 retValue = v[itemNum][0]+v[itemNum][2];
174 break;
175 case 3:
176 if(money >= GetNeededMoney(itemNum,scheme))
177 retValue = v[itemNum][0]+v[itemNum][1]+v[itemNum][2];
178 break;
179 case 4:
180 retValue = 0;
181 break;
182 }
183 return retValue;
184}
185
186
187
188//获得剩余的residualMoney钱(体积)和前itemNum个物品时能够得到的最大价值
189int MaxValue(int residualMoney,int itemNum)
190{
191 //申明返回的最大价值
192 int bestValue = 0;
193
194 //如果这个价值已经算出来了 [这里对因动态规划的“做备忘录”]
195 if(maxValue[residualMoney][itemNum]!=-1)
196 {
197 bestValue = maxValue[residualMoney][itemNum];
198 }
199 else if(itemNum==0) //否则如果这个物品是最后一个 [这里对应动态规划的“边界”]
200 {
201 //用仅有的residualMoney个钱考虑5种购买方案,并把获得的最大价值放在bestValue中
202 for(int i=0; i<5; i++)
203 {
204 bestValue = max(bestValue,GetSchemeMoney(residualMoney,itemNum,i));
205 }
206
207 }
208 else//否则不是最后一个物品 [这里对应动态规划的“最优子结构”]
209 {
210 //考虑此物品的5种购买方案
211 for(int i=0; i<5; i++)
212 {
213 int useMoney = GetNeededMoney(itemNum,i);
214 if(residualMoney >= useMoney)
215 {
216 bestValue = max(bestValue, GetSchemeMoney(useMoney,itemNum,i) + MaxValue(residualMoney - useMoney,itemNum-1) );
217 }
218 }
219 }
220
221 maxValue[residualMoney][itemNum] = bestValue;
222 return bestValue;;
223}
224
225
226
227
228int main(int argc, char *argv[])
229{
230 //初始化数据
231 init();
232 //输出在仅有n个钱(体积)时,对于前m-1个物品能够得到的最大价值,注意是从0开始编号,所以m-1是最后一个物品
233 cout<<MaxValue(n,m-1);
234
235 system("PAUSE");
236 return EXIT_SUCCESS;
237}
238