邮票问题--多重背包

【USACO3.1.6】邮票

Description

  已知一个 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票。计算从 1 到 M 的最大连续可贴出的邮资。

  例如,假设有 1 分和 3 分的邮票;你最多可以贴 5 张邮票。很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难:

       6 = 3 + 3
       7 = 3 + 3 + 1
       8 = 3 + 3 + 1 + 1
       9 = 3 + 3 + 3
       10 = 3 + 3 + 3 + 1
       11 = 3 + 3 + 3 + 1 + 1
       12 = 3 + 3 + 3 + 3
       13 = 3 + 3 + 3 + 3 + 1。

  然而,使用 5 枚 1 分或者 3 分的邮票根本不可能贴出 14 分的邮资。因此,对于这两种邮票的集合和上限 K=5,答案是 M=13。

Input

  第 1 行: 两个整数,K 和 N。K(1 <= K <= 200)是可用的邮票总数。N(1 <= N <= 50)是邮票面值的数量。
  第 2 行 .. 文件末: N 个整数,每行 15 个,列出所有的 N 个邮票的面值,面值不超过 10000。

Output

  一个整数,从 1 分开始连续的可用集合中不多于 K 张邮票贴出的邮资数。

Sample Input

  5 2

  1 3

Sample Output

  13

我用两种动态规划编了,一种是判定的动态规划,一种是直接求值的动态规划。判定的动态规划如下:

代码
1 #include<stdio.h>
2 #include<stdlib.h>
3  int f[20000001]={0},n,k,max=0;
4  int sum[20000001]={0};
5  int a[51];
6
7  int main(){
8 FILE *in,*out;
9 int i,j,i1,flag=0;
10 in=fopen("input9.txt","r");
11 out=fopen("output.txt","w");
12 fscanf(in,"%d%d",&k,&n);
13 for(i=1;i<=n;i++)
14 {
15 fscanf(in,"%d",&a[i]);
16 if(max<a[i])max=a[i];
17 }
18
19
20 for(i=0;i<=k;i++)
21 {
22 f[i*a[1]]=1;
23 sum[i*a[1]]=i;
24 }
25
26
27 for(j=1;j<=max*k;j++)
28 for(i=2;i<=n;i++)
29 for(i1=0;i1<=k;i1++)
30 {
31 if(j-i1*a[i]>=0&&f[j]==0)
32 {
33 if(f[j-i1*a[i]]&&sum[j-i1*a[i]]+i1<=k)
34 {
35 f[j]=1;
36 sum[j]=sum[j-i1*a[i]]+i1;
37 }
38 }
39 if(j-i1*a[i]>=0&&f[j]==1&&sum[j]>sum[j-i1*a[i]]+i1)
40 sum[j]=sum[j-i1*a[i]]+i1;
41 }
42
43
44 i=1;
45 while(f[i]==1)i++;
46
47 fprintf(out,"%d\n",i-1);
48 printf("%d\n",i-1);
49 system("pause");
50 fclose(in);
51 fclose(out);
52 return 0;
53 }


但是这个程序在数据规模相当大时就过不了。有这么三组数据:

input1:

 200 14
1 2 4 15 9 31 63 2100 3500 127 255 511 1000 1999

input2:

200 50
1 37 87 14 137 4016 157 5383 7318 8662 7074 2821 5250 9704 8986
1375 6587 5962 7190 339 1981 9719 7012 385 7926 5159 3259 5144 7846 8854
3249 3198 8408 2784 6249 4648 6799 2757 31 4116 7771 3456 3288 3020 3159
8625 747 9745 938 10000
input3:

200 15
1 3 14 59 265 358 979 323 846 26 433 832 7950 28 841

 

output1:682938

output2:1996089

output3:1525524

希望有人可以提出改进意见。另一种直接求值的动态规划如下:

 

代码
1 #include<stdio.h>
2 #include<stdlib.h>
3 int f[20000001]={0},n,k,max=0;
4 int a[51];
5
6 int main(){
7 FILE *in,*out;
8 int i,j,i1,flag=0;
9 in=fopen("input10.txt","r");
10 out=fopen("output.txt","w");
11 fscanf(in,"%d%d",&k,&n);
12 for(i=1;i<=n;i++)
13 {
14 fscanf(in,"%d",&a[i]);
15 if(max<a[i])max=a[i];
16 }
17 f[0]=0;
18 for(j=1;j<=k*max;j++)
19 f[j]=k+1;
20 for(j=1;j<=k*max;j++)
21 {
22 for(i=1;i<=n;i++)
23 if(j>=a[i])
24 if(f[j]>f[j-a[i]]+1)f[j]=f[j-a[i]]+1;
25 if(f[j]>k){max=j-1; break;}
26 }
27 fprintf(out,"%d\n",max);
28 printf("%d\n",max);
29 system("pause");
30 fclose(in);
31 fclose(out);
32 return 0;
33 }

 f[j]代表:拼出j最少用的邮票数

 转移方程:f[j]=min{f[j-a[i]]+1}
 程序思想:每次找到拼出j的最少邮票数,赋值给f[j],如果j的最小邮票数大于k,则结束循环,此时j-1为所求值。
 注意:由于邮票张数没有限制,所以使用完全背包的处理方法,将两重循环倒着写。

 

 

posted @ 2010-07-23 17:32  Danty  阅读(614)  评论(0编辑  收藏  举报