这个多重背包问题我单纯地把它转化成了0-1背包问题,然后列出状态转移方程 dp[i][j] = max{ dp[ i-1][ j - k * V[i]] + k , dp[ i-1][ j-1] }
这样看起来是需要三重循环哦。。开心的列了出来【图样图森破,简直拿衣服】
DP部分的代码写成了这样
for( i = 1; i <= n; i++){
for( j = N; j >= bill[i].mon; j--){
temp = 0;
for( k = 1; k <= bill[i].fre; k++){
if( j-k*bill[i].mon < 0) break;
if( dp[j] < dp[j-k*bill[i].mon] + k)
dp[j] = dp[j-k*bill[i].mon] + k;
}
}
}
之后我猛然发现一个问题,对于给出的数据中取不到的值也会计算成使 dp[j] 最大的那个 j 值....比如对于输入数据 633 4 500 30 6 100 1 5 0 1 。应得数据应该为630,但是实际给出的还是633。。。
我改了以后发现原来就算是630之前也会连续取一样的值。。。
那么如果记录最大值呢?然后我写成了这样。。。
for( i = 1; i <= n; i++){
for( j = N; j >= bill[i].mon; j--){
temp = 0;
for( k = 1; k <= bill[i].fre; k++){
if( j-k*bill[i].mon < 0) break;
if( dp[j] < dp[j-k*bill[i].mon] + k)
dp[j] = dp[j-k*bill[i].mon] + k;
temp = k*bill[i].mon;
}
if( temp > max) max = temp;
}
}
记录最大值这个思路是对的,但是我写错了,这个程序明显是求至票面额倍数的最大值。。。这里有点想当然了,没有进一步思考,下次要吸取教训
不过既然记录了最大值,而且也不需要求最大的张数,我们也就不需要记忆每一个钱数的张数了。那么只需要一个数组来记录某值是否被访问到就可以了。于是我就看懂了网上一部分人的题解23333333.。。。参考了别人的题解,我终于做出来了
下面放上AC题解之一【用dp来存储是否被访问到】
#include<stdio.h>
#include<string.h>
struct A{
int mon;
int fre;
}bill[15];
int main(void){
int N, n, i, j, k, temp, max;
int dp[100005];
while( scanf("%d", &N) != EOF){
memset( dp, 0, sizeof(dp));
scanf("%d", &n);
for( i = 1; i <= n; i++){
scanf("%d%d", &bill[i].fre, &bill[i].mon);
}
if( n == 0 || N == 0){
printf("0\n"); continue;
}
max = 0;
dp[0] = 1;
for( i = 1; i <= n; i++){ // 对每一个bill进行遍历,之前我还弄反了
for( j = max; j >= 0; j--){
if( dp[j])
for( k = 1; k <= bill[i].fre; k++){
temp = j + k*bill[i].mon; // 能够取到的值
if( temp > N)continue;
dp[temp] = 1; // 记录,已经访问
if( temp > max) max = temp;// 取最大值
}
}
}
printf("%d\n", max);
}
return 0;
}
事实上我之前是被之前自己写的转移方程固化了思维,没有想到这道题还可以这样想。
之前的状态转移方程应该是对的。。。。张数什么的都是能算出来的。。。要是换一个问法也许我就能AC了。。。。嘤嘤嘤(并不能,因为我还落后地使用着二维数组。。所以可能会超时T0T)