我们有n个邮票,其中每个邮票我有一个特定面额d(i)和尺寸s(i)。所有面额都是不同的,我们可以多次使用邮票面额。现在我想设计一个算法,给定d(i)和s(i)的邮票和邮资金额p,找到其面额将恰好加到p的最小邮票总数?
我知道这是一个动态编程问题,而且我觉得它应该像背包问题一样被解决。但我完全感到困惑,因为这里邮票的最小总尺寸应该加到p。我想出了下面的重复,我知道这不会是真的,因为它不会检查总的最小尺寸是否合计为p:
M(p)=min{M(p-d(i))}+s(i),M(p-d(i))} for i from 1 to n
另外我不知道如何制表(为了编写动态编程的迭代版本)。
我的猜测是我必须有一个尺寸为p和d(i)的二维数组,每个单元格都用s(i)填充。
答案 0 :(得分:2)
你的猜测是正确的。这是二维DP问题。但在我们宣布我们需要达到递推公式之前,我们会看到该公式中有多少个字段在变化。
在您的问题陈述中有两件事:1)邮票大小需要最小化。 2)所有邮票应加起来为P.如果你是初学者,请不要认为DP会立刻自下而上。首先考虑自上而下的递归方法,这应该在一些练习后变得容易思考。在这种情况下,让我们说你知道N个邮票的解决方案。让我们将这个解决方案表示为M(d(N),P),它表示M是N个标记中的解,其总和为P.要获得递归关系,请考虑如果最后一个标记(第N个)不是结果的一部分那么问题将减少为从N-1邮票中找到P.如果存在最后一个元素(第N个标记),则问题是从N-1个标记中找出P-d(N)和。其反复出现的关系如下:
M(N, P) = Min{ M(N-1, P), M(N-1, P - d(N))}
或更一般意义上说:
M(i, P) = Min{ M(i - 1, P), M(i - 1, P - d(i))}
正如您所看到的,这个递归公式中的两个字段不同,因此您必须考虑二维DP。
取两个轴,在X轴上取0到P全部和,在y轴取0到N(元素个数)。 迭代函数应如下所示。
set all M(0, j) and M(i, 0) = 0 for all i [0, N] and j [0, P]
for: i = 0 to N
for: j = 0 to P
for: int k = 0 to j
if: j - P(k) >= 0 and M(i, j) < M(i-1, j-P(k))
M(i, j) = M(i-1, j-P(k));
return M(N, P);
注意:我没有提到邮票的大小,因为很明显M中的字段将是那些需要最小化的选定邮票的大小。
答案 1 :(得分:1)
以下是DP复发关系,其中包含两个要求。首先你的问题类似于背包问题,只需要完全填充背包所需的更改,此处容量为p,(s(i),d(i))
为项目类型。
DP解决方案: -
考虑n
个项目(s(i),d(i))
和邮资金额p
,Valid(n,p)
表示子问题的解决方案恰好合计为p
: -
DP(n,p) = 0;
Valid(n,p) = 0;
if(Valid(n-1,p)) {
DP(n,p) = DP(n-1,p);
Valid(n,p) = true;
}
if(Valid(n,p-d(n))) {
DP(n,p) = min{DP(n,p),DP(n,p-d(n)) + s(n)};
Valid(n,p) = true;
}
最终结果:
if(Valid(n,p)) {
return(DP(n,p));
}
else printf("no solution");
使用自下而上的DP解决方案: -
Valid[n][p+1] = {false};
DP[n][p+1] = {0};
Valid[0][0] = true;
DP[0][0] = 0;
for(int i=0;i<=p;i++) {
if(s[0]<=i) {
if(Valid[0][i-d[0]]) {
DP[0][i] = s[0] + DP[0][i-d[0]];
if(s[0]==i)
Valid[0][i] = true;
}
}
}
for(int j=0;j<n;j++)
Valid[j][0] = true;
for(int j=1;j<n;j++) {
for(int k=1;k<=p;k++) {
if(Valid[j-1][k]) {
DP[j][k] = DP[j-1][k];
Valid[j][k] = true;
}
if(Valid[j][k-d(j)]) {
DP[j][k] = min{DP[j][k],DP[j][k-d(j)] + s[k]};
Valid[j][k] = true;
}
}
}