通过金矿模型介绍动态规划(介绍了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
12
E-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
我们可以通过以下几个关键从而把问题看得简单一些:
25
1、既然买附件时一定要买主件,那么可以认为每个主件有4种购买情况(分别是只买主件,买主件和附件1,买主件和附件2,买主件和两个附件), 而不用单独考虑附件是否购买的情况。
26
这样我们就把如何在众多主件与附件之中选择购买的问题转变为如何在主件中选择购买的问题了。对于每个主件,其实还有不买的情况,我们把这种情况和上面4种看成购买的5种方案(方案0是只买主件,方案1是买主件和附件1,方案2是买主件和附件2,方案3是买主件和两个附件,方案4是什么都不买)。
27
2、有些主件有附件,而有些没有,这为我们思考带来了负担,我们完全可以假设任何主件都有两个附件,也就是说如果题目没有给出某个主件的附件的话,我们就假设这个主件的附件是存在的,且价格和重要度都等于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
42
using namespace std;
43
44
const int maxm=60;//程序支持的最大物品数
45
const int maxn=32000;//程序支持的最大体积
46
47
int n;//钱数
48
int m;//物品数(也就是主件的个数)
49
int v[maxm][3];//v[i][0]表示第i个物品的主件价值, v[i][1]表示第i个物品的第一个附件的价值,v[i][2]表示第i个物品的第二个附件的价值
50
int w[maxm][3];//与v差不多,不过w表示的是体积
51
int maxValue[maxn][maxm];//maxValue[i][j]保存了给定i个空间和前j个物品(说道物品,均包含主件和两个附件,下同)能够获得的最大价值总合。
52
53
//初始化数据
54
void 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个物品至少需要的钱数
127
int 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种购买方案时能够得到的最大价值
156
int 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个物品时能够得到的最大价值
189
int 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
228
int 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
昵称:SDJL10
编写时间:2008年8月11
联系QQ:4456190712
E-Mail:44561907@qq.com13
获得更多文章请访问我的博客:www.cnblogs.com/sdjl14
如果发现BUG或有写得不好的地方请发邮件告诉我:)15
转载请保留此信息:) 16
=========题目解析========17
此题是01背包问题的变形,如果读者对01背包问题还不熟悉的话请看先我的另一篇关于01背包问题的文章。18
下面我将假设你已经了解了01背包问题。19

20
题目补充点一:为了让程序容易看懂些,我们忽略题目中的价格都是10的这个条件,其实这个条件可以减少一些时间和空间的开销。 21

22
我们把此题看成背包问题,物品的重要度乘以价格是背包问题中的价值,以后说道价值就是指这两个值的乘积,物品的价格是背包问题中的体积,同样以后说道体积就是说物品的价格。 23

24
我们可以通过以下几个关键从而把问题看得简单一些:25
1、既然买附件时一定要买主件,那么可以认为每个主件有4种购买情况(分别是只买主件,买主件和附件1,买主件和附件2,买主件和两个附件), 而不用单独考虑附件是否购买的情况。26
这样我们就把如何在众多主件与附件之中选择购买的问题转变为如何在主件中选择购买的问题了。对于每个主件,其实还有不买的情况,我们把这种情况和上面4种看成购买的5种方案(方案0是只买主件,方案1是买主件和附件1,方案2是买主件和附件2,方案3是买主件和两个附件,方案4是什么都不买)。 27
2、有些主件有附件,而有些没有,这为我们思考带来了负担,我们完全可以假设任何主件都有两个附件,也就是说如果题目没有给出某个主件的附件的话,我们就假设这个主件的附件是存在的,且价格和重要度都等于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

42
using namespace std;43

44
const int maxm=60;//程序支持的最大物品数 45
const int maxn=32000;//程序支持的最大体积 46

47
int n;//钱数 48
int m;//物品数(也就是主件的个数) 49
int v[maxm][3];//v[i][0]表示第i个物品的主件价值, v[i][1]表示第i个物品的第一个附件的价值,v[i][2]表示第i个物品的第二个附件的价值 50
int w[maxm][3];//与v差不多,不过w表示的是体积 51
int maxValue[maxn][maxm];//maxValue[i][j]保存了给定i个空间和前j个物品(说道物品,均包含主件和两个附件,下同)能够获得的最大价值总合。 52

53
//初始化数据54
void 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开始,所以要减去191
int mainOrder = mainItemNum[q - 1];92
//如果这个主件的附件1不存在93
if(v[mainOrder][1] == -1)94
{95
//设置为附件196
v[mainOrder][1] = o * p;97
w[mainOrder][1] = o; 98
}99
else//否则这个主件的附件1存在100
{101
//设置为附件2102
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个物品至少需要的钱数127
int 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种购买方案时能够得到的最大价值156
int 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个物品时能够得到的最大价值189
int 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

228
int 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



浙公网安备 33010602011771号