hdu 1114Piggy-Bank(完全背包)

传送门

参考资料:

  [1]:https://www.cnblogs.com/jbelial/articles/2116074.html

  [2]:https://www.luogu.org/problemnew/solution/P1616

题意:

  有一个小猪存钱罐,里面有各式各样的硬币,每种硬币有不同的面值和重量。

  现测量出存钱罐初始的重量(E)和存满钱时的重量(F),问在这N种硬币中,如何选取,使得选取的硬币的总重量恰好等于(F-E),且总价值最小。

  如果可以找到,输出"The minimum amount of money in the piggy-bank is num".(num : 总和最小的面值)

  如果不能通过组合使得硬币总重量恰好等于(F-E),则输出"This is impossible."

分析:

  乍一看,和“01”背包很想,所不同的是,在“01”背包中,第 i 种物品只有两种选择,拿或者不拿;

  而此题,第 i 种面值的硬币可不止有拿与不拿这两种选择,而是有拿0,1,2,.....,k个,共(k+1)种选择,其中k满足 k*w[i] <= (F-E);

  这种每种物品都有无限件可用的问题,称为“完全背包”问题。

题解:

  1.完全背包转“01”背包

    思路:将一种物品拆成多件物品。

    考虑到第 i 种物品最多选 (F-E) / w[ i ] 件;

    于是可以把第 i 种物品转化为 (F-E) / w[ i ] 件费用及价值均不变的物品,然后求解这个01背包问题。

    如果开个二维的dp数组,指定不可行,具体为什么,请自行思考;

    提示:假设每种硬币都可拆成 k 个(k 最大可达 10000),N种硬币则可拆成 N*k 个(N最大为500),所以最坏的情况是可拆成 N*k = 5e6 个物品。

    比较好用的方法就是使用滚动数组优化空间。

    更高效的转化方法是:把第 i 种物品拆成重量为 w[i]×2k、价值为 p[i]×2k 的若干件物品,其中k满足 w[i]×2k < (F-E)。

    这是二进制的思想,因为不管最优策略选几件第 i 种物品,总可以表示成若干个 2k 件物品的和。

    这样把每种硬币拆成 log2( (F-E) / w[i]) ) 件物品,是一个很大的改进。

AC代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 #define INF 0x3f3f3f3f
 5 const int maxn=500+50;
 6 
 7 int n;
 8 int e,f;
 9 int p[maxn],w[maxn];
10 int dp[10010];
11 
12 void Solve()
13 {
14     int v=f-e;
15     for(int j=1;j <= v;++j)
16         dp[j]=INF;
17     dp[0]=0;
18 
19     for(int i=1;i <= n;++i)
20     {
21         for(int k=0,cur=(1<<k)*w[i];cur <= v;++k)///第i个物品选2^k个
22         {
23             for(int j=v;j >= cur;--j)
24                 dp[j]=min(dp[j],dp[j-cur]+(1<<k)*p[i]);
25 
26             cur <<= 1;
27         }
28     }
29     if(dp[v] < INF)
30         printf("The minimum amount of money in the piggy-bank is %d.
",dp[v]);
31     else
32         printf("This is impossible.
");
33 }
34 int main()
35 {
36     int test;
37     scanf("%d",&test);
38     while(test--)
39     {
40         scanf("%d%d",&e,&f);
41         scanf("%d",&n);
42         for(int i=1;i <= n;++i)
43             scanf("%d%d",p+i,w+i);
44         Solve();
45     }
46     return 0;
47 }
View Code

  2.我们有更优的O(VN)的算法

    定义dp[ i ][ j ] : 前 i 件物品恰好组成重量 j 的最小面值;

    第 i 件物品的状态转移方程为:

1 for(int j=w[i];j <= v;++j)
2     dp[j]=min(dp[j],dp[j-w[i]]+p[i]);

    完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第 i 种物品”这种策略时,

    却正需要一个可能已选入第 i 种物品的子结果 dp[ i ][ v-w[i] ],所以就可以并且必须采用 j = w[i]..v 的顺序循环。

    以上思路摘抄自[1]%%%%%%%%%%%%%%%

AC代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define ll long long
 4 #define INF 0x3f3f3f3f
 5 const int maxn=500+50;
 6 
 7 int n;
 8 int e,f;
 9 int p[maxn],w[maxn];
10 int dp[10010];
11 
12 void Solve()
13 {
14     int v=f-e;
15     for(int j=1;j <= v;++j)
16         dp[j]=INF;
17     dp[0]=0;
18 
19     for(int i=1;i <= n;++i)
20         for(int j=w[i];j <= v;++j)
21             dp[j]=min(dp[j],dp[j-w[i]]+p[i]);
22 
23     if(dp[v] < INF)
24         printf("The minimum amount of money in the piggy-bank is %d.
",dp[v]);
25     else
26         printf("This is impossible.
");
27 }
28 int main()
29 {
30     int test;
31     scanf("%d",&test);
32     while(test--)
33     {
34         scanf("%d%d",&e,&f);
35         scanf("%d",&n);
36         for(int i=1;i <= n;++i)
37             scanf("%d%d",p+i,w+i);
38         Solve();
39     }
40     return 0;
41 }
View Code
原文地址:https://www.cnblogs.com/violet-acmer/p/9890254.html