微信扫一扫打赏支持

算法与数据结构---3、砝码称重

算法与数据结构---3、砝码称重

一、总结

一句话总结:

砝码称重有基本的枚举解法,也有对应的01背包和多重背包的解法,对背包我们可以进行空间优化,对这题也可以进行bitset优化
/*

C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,
它的每一个元素只能是0或1,每个元素仅用1bit空间

001000010010001000100100010001


如果选择状态:
f[i][j]表示前i件物品中总重量为j方案的方案是否存在

那么f[i][j]里面存的数据也就是0和1,所以可以用bitset来代替

二维的状态转移方程空间优化之后
f[i][j]=f[i-1][j] || f[i-1][j-w[i]];
就变成一维的
f[j]=[j] || [j-w[i]];

解决j-w[i]>=0之后
f[j+a[i]]=f[j+a[i]] || f[j];
空间优化之后,也可以写成
if(f[j]) f[j+a[i]]=1;


这个时候,f[]这个数组就可以用bitset来代替了


0000000000000000000000000000001

注意:
for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
    for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
        for(int j=weight;j>=a[i];j--){
            f[j]=f[j] || f[j-a[i]];
        }
    }
}
使用bitset优化砝码称重的多重背包解法的时候,注意点是什么
要解决bitset去掉的那层循环的限制条件,也就是第三层循环中的j>=a[i],
可以把f[j]=f[j]||f[j-a[i]]中j的位置都加上a[i]即可解决

在砝码称重的bitset的优化中,bitset的左移和右移操作如何确定
由砝码称重这题对应的关系,if(f[j]) f[j+a[i]]=1,由f[j]得到f[j+a[i]],那么肯定是左移

*/


#include <iostream>
#include <bitset>
using namespace std;
bitset<1005> f;
int main(){
    
    int n[7];
    int a[7]={0,1,2,3,5,10,20};
    for(int i=1;i<=6;i++){
        cin>>n[i];
    }
    //2、初始化动态规划数组,做动态规划
    f[0]=1;
    for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
        for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
            f=f | f<<a[i];
        }
    }
    //3、统计方案总数

    cout<<"Total="<<(f.count()-1)<<endl;
    return 0;
}

 

 

1、使用bitset优化砝码称重的多重背包解法的时候,注意点是什么?

a、要解决bitset去掉的那层循环的限制条件,也就是第三层循环中的j>=a[i],
b、可以把f[j]=f[j]||f[j-a[i]]中j的位置都加上a[i]即可解决

|||-begin

for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
    for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
        for(int j=weight;j>=a[i];j--){
            f[j]=f[j] || f[j-a[i]];
        }
    }
}

|||-end

 

2、在砝码称重的bitset的优化中,bitset的左移和右移操作如何确定?

由砝码称重这题对应的关系,if(f[j]) f[j+a[i]]=1,由f[j]得到f[j+a[i]],那么肯定是左移

 

 

 

二、砝码称重

博客对应课程的视频位置:

3.1、砝码称重-枚举法
https://www.fanrenyi.com/video/27/254

3.2、砝码称重-01背包
https://www.fanrenyi.com/video/27/255

3.3、砝码称重-01背包2
https://www.fanrenyi.com/video/27/256

3.4、砝码称重-01背包空间优化
https://www.fanrenyi.com/video/27/257

3.5、砝码称重-多重背包
https://www.fanrenyi.com/video/27/258

3.6、砝码称重-进一步优化
https://www.fanrenyi.com/video/27/259

3.7、砝码称重-bitset优化
https://www.fanrenyi.com/video/27/262

 

 

1、题目需求

砝码称重(NOIP1996)
【问题描述】
设有1g、2g、3g、5g、10g、20g的砝码各若干枚(其总重<=1000),
求用这些砝码能称出不同的重量的个数。
【输入文件】
        输入1g、2g、3g、5g、10g、20g的砝码个数
【输出文件】
        能称出不同的重量的个数
【输入样例】
        1 1 0 0 0 0
【输出样例】
        3

 

题目提交位置
https://www.luogu.com.cn/problem/P2347

 

2、枚举法解法

 1 /*
 2 
 3 分析
 4 根据输入的砝码信息,每种砝码可用的最大个数是确定的,而且每种砝码的个数是连续的,
 5 能取0到最大个数,所以,符合穷举法的两个条件,可以使用穷举法。
 6 
 7 穷举时,重量可以由1g,2g,……,20g的砝码中的任何一个,或者多个构成,
 8 枚举对象可以确定为6种重量的砝码,范围为每种砝码的个数,判定时,
 9 只需判断这次得到的重量是新得到的,还是前一次已经得到的,即判重。
10 由于总重<=1000,所以,可以开一个flag[1..1000]的布尔数组来判重,
11 当得到v重量时,把flag[v]置为true,下次再得到v时,还是置true,
12 最后只需遍历一下flag数组,即可得到重量的个数。
13 
14 枚举变量:1g砝码,2g砝码,3g砝码,5g砝码,10g砝码,20g砝码
15 枚举范围:1g砝码0-n1,2g砝码0-n2,3g砝码0-n3,5g砝码0-n5,10g砝码0-n10,20g砝码0-n20
16 枚举判断条件:
17 统计所有砝码和的不同重量
18 砝码和总重小于1000
19 
20 算法思路:
21 1、枚举不同砝码的个数,计算总重量,并将总重量对应的标志置为1
22 2、根据标志,计算总重量的个数
23 
24 
25 100 100 150 250 100 300
26 100*50*50*50*10*15
27 
28 */
29 #include <iostream>
30 using namespace std;
31 int flag[1005] = {0};
32 int main()
33 {
34     int n1, n2, n3, n5, n10, n20;
35     cin >> n1 >> n2 >> n3 >> n5 >> n10 >> n20;
36     //1、枚举不同砝码的个数,计算总重量,并将总重量对应的标志置为1
37     for (int i1 = 0; i1 <= n1; i1++)
38     {
39         for (int i2 = 0; i2 <= n2; i2++)
40         {
41             for (int i3 = 0; i3 <= n3; i3++)
42             {
43                 for (int i5 = 0; i5 <= n5; i5++)
44                 {
45                     for (int i10 = 0; i10 <= n10; i10++)
46                     {
47                         for (int i20 = 0; i20 <= n20; i20++)
48                         {
49                             int sum=i1+i2*2+i3*3+i5*5+i10*10+i20*20;
50                             flag[sum]=1;
51                         }
52                     }
53                 }
54             }
55         }
56     }
57     //2、根据标志,计算总重量的个数
58     int count=0;
59     for(int i=1;i<=1000;i++){
60         if(flag[i]) count++;
61     }
62     cout<<"Total="<<count<<endl;
63     return 0;
64 }

 

 

 

3、01背包解法

 1 /*
 2 砝码称重(NOIP1996)
 3 
 4 【问题描述】
 5 设有1g、2g、3g、5g、10g、20g的砝码各若干枚(其总重<=1000),
 6 求用这些砝码能称出不同的重量的个数。
 7 【输入文件】
 8         输入1g、2g、3g、5g、10g、20g的砝码个数
 9 【输出文件】
10         能称出不同的重量的个数
11 【输入样例】
12         1 1 0 0 0 0 
13 【输出样例】
14         3
15 
16 
17 分析:
18 
19 比如2 1 2 1 1 1的输入数据,就是表示1 1 2 3 3 5 10 20
20 
21 问题就转化为 1 1 2 3 3 5 10 20 这些砝码,
22 对里面的每一个取或者不取,可以组成多少个总重量
23 那么这就是一个非常标准的01背包问题
24 
25 f[i][j]表示前i件物品中总重量为j方案的方案是否存在
26 
27 如 f[4][5]就是表示前4件物品中总重量为5的方案是否存在
28 
29 状态转移方程:
30 当第i件物品不取的时候:
31 如果f[i-1][j]存在 f[i][j]=1
32 当第i件物品取的时候:
33 如果f[i-1][j-w[i]]存在,f[i][j]=1
34 
35 所以f[i][j]=f[i-1][j] || f[i-1][j-w[i]];
36 初始状态:
37 f[k][0]=1
38 终止状态:
39 设砝码的总个数为num个
40 f[num][1000]
41 当然这个还不是直接所求的,
42 直接所求就是求1到1000的过程中,哪一些砝码总数的方案不为0即可
43 
44 
45 算法思路:
46 1、统计砝码总数,准备好砝码序列
47 2、初始化动态规划数组,做动态规划
48 3、统计方案总数
49 
50 
51 */
52 #include <iostream>
53 using namespace std;
54 int f[1005][1005]={0};
55 int main(){
56     //1、统计砝码总数,准备好砝码序列
57     int num=0;//砝码总数
58     int w[1005];//砝码序列
59     int a[7]={0,1,2,3,5,10,20};
60     for(int i=1;i<=6;i++){
61         int x;
62         cin>>x;
63         for(int j=1;j<=x;j++) w[++num]=a[i];
64     }
65     //2、初始化动态规划数组,做动态规划
66     for(int i=0;i<=1000;i++) f[i][0]=1;
67     for(int i=1;i<=num;i++){
68         for(int j=1;j<=1000;j++){
69             if(j-w[i]>=0)
70             f[i][j]=f[i-1][j] || f[i-1][j-w[i]];
71             else
72             f[i][j]=f[i-1][j];
73         }
74     }
75     //3、统计方案总数
76     int count=0;
77     for(int i=1;i<=1000;i++){
78         if(f[num][i]) count++;
79     }
80     cout<<"Total="<<count<<endl;
81     return 0;
82 }

 

 

4、01背包另一种思路

 1 /*
 2 
 3 分析:
 4 
 5 f[i][j]表示前i件物品中总重量为j方案的方案是否存在
 6 
 7  8 
 9 f[i][j]表示前i件物品中总重量为j方案总数
10 
11 
12 ==================================================
13 ==================================================
14 
15 
16 比如2 1 2 1 1 1的输入数据,就是表示1 1 2 3 3 5 10 20
17 
18 问题就转化为 1 1 2 3 3 5 10 20 这些砝码,
19 对里面的每一个取或者不取,可以组成多少个总重量
20 那么这就是一个非常标准的01背包问题
21 
22 f[i][j]表示前i件物品中总重量为j方案总数
23 如 f[4][5]就是表示前4件物品中总重量为5的方案总数
24 (这里我们设置状态设置的是总重量为j方案总数,
25 方案总数只要大于等于1,那么就说明重量为j的方案是存在的)
26 
27 状态转移方程:
28 当第i件物品不取的时候:
29 f[i][j]=f[i-1][j]
30 当第i件物品取的时候:
31 f[i][j]=f[i-1][j-w[i]]
32 
33 所以f[i][j]=f[i-1][j] + f[i-1][j-w[i]];
34 初始状态:
35 f[k][0]=1
36 终止状态:
37 设砝码的总个数为num个
38 f[num][1000]
39 当然这个还不是直接所求的,
40 直接所求就是求1到1000的过程中,哪一些砝码总数的方案不为0即可
41 
42 
43 算法思路:
44 1、统计砝码总数,准备好砝码序列
45 2、初始化动态规划数组,做动态规划
46 3、统计方案总数
47 
48 */
49 
50 
51 #include <iostream>
52 using namespace std;
53 int f[1005][1005]={0};
54 int main(){
55     //1、统计砝码总数,准备好砝码序列
56     int num=0;//砝码总数
57     int w[1005];//砝码序列
58     int a[7]={0,1,2,3,5,10,20};
59     for(int i=1;i<=6;i++){
60         int x;
61         cin>>x;
62         for(int j=1;j<=x;j++) w[++num]=a[i];
63     }
64     //2、初始化动态规划数组,做动态规划
65     for(int i=0;i<=1000;i++) f[i][0]=1;
66     for(int i=1;i<=num;i++){
67         for(int j=1;j<=1000;j++){
68             if(j-w[i]>=0)
69             f[i][j]=f[i-1][j] + f[i-1][j-w[i]];
70             else
71             f[i][j]=f[i-1][j];
72         }
73     }
74     //3、统计方案总数
75     int count=0;
76     for(int i=1;i<=1000;i++){
77         if(f[num][i]) count++;
78     }
79     cout<<"Total="<<count<<endl;
80     return 0;
81 }

 

 

5、01背包的空间优化

 1 /*
 2 
 3 分析:
 4 
 5 01背包本身是可以进行空间优化的
 6 因为动态规划本质上是填表
 7 f[i][j]表示前i件物品中总重量为j方案总数
 8 f[i][j]=f[i-1][j] + f[i-1][j-w[i]]; 
 9 填表的顺序为:
10 i是从1-num
11 j是从1-1000
12 是用的2维的表格
13 01背包问题用一维表格也可以实现保存中间状态
14 具体实现就是去掉i这一维,
15 
16 f[j]=f[j] + [j-w[i]]; 
17 只不过这个时候,填表的顺序就是
18 i是从1-num
19 j是从1000-1
20 
21 */
22 #include <iostream>
23 using namespace std;
24 int f[1005]={0};
25 int main(){
26     //1、统计砝码总数,准备好砝码序列
27     int num=0;//砝码总数
28     int w[1005];//砝码序列
29     int a[7]={0,1,2,3,5,10,20};
30     for(int i=1;i<=6;i++){
31         int x;
32         cin>>x;
33         for(int j=1;j<=x;j++) w[++num]=a[i];
34     }
35     //2、初始化动态规划数组,做动态规划
36     f[0]=1;
37     for(int i=1;i<=num;i++){
38         for(int j=1000;j>=1;j--){
39             if(j-w[i]>=0)
40             f[j]=f[j] + f[j-w[i]];
41         }
42     }
43     //3、统计方案总数
44     int count=0;
45     for(int i=1;i<=1000;i++){
46         if(f[i]) count++;
47     }
48     cout<<"Total="<<count<<endl;
49     return 0;
50 }

 

 

6、多重背包解法

 1 /*
 2 砝码称重(NOIP1996)
 3 
 4 【问题描述】
 5 设有1g、2g、3g、5g、10g、20g的砝码各若干枚(其总重<=1000),
 6 求用这些砝码能称出不同的重量的个数。
 7 【输入文件】
 8         输入1g、2g、3g、5g、10g、20g的砝码个数
 9 【输出文件】
10         能称出不同的重量的个数
11 【输入样例】
12         1 1 0 0 0 0 
13 【输出样例】
14         3
15 
16 
17 分析
18 这个问题本身的描述就是一个多重背包,
19 也就是每个砝码有多个
20 
21 多重背包就不需要01背包里面的 1、统计砝码总数,准备好砝码序列
22 
23 多重背包可以在01背包的基础上稍微改一下就实现了
24 
25 */
26 
27 #include <iostream>
28 using namespace std;
29 int f[1005]={0};
30 int main(){
31     
32     int n[7];
33     int a[7]={0,1,2,3,5,10,20};
34     for(int i=1;i<=6;i++){
35         cin>>n[i];
36     }
37     //2、初始化动态规划数组,做动态规划
38     f[0]=1;
39     for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
40         for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
41             for(int j=1000;j>=a[i];j--){
42                 f[j]=f[j] + f[j-a[i]];
43             }
44         }
45     }
46     //3、统计方案总数
47     int count=0;
48     for(int i=1;i<=1000;i++){
49         if(f[i]) count++;
50     }
51     cout<<"Total="<<count<<endl;
52     return 0;
53 }

 

7、进一步优化

 1 /*
 2 
 3 题中说总重<=1000,所以我们的动态规划根据这个1000做循环,
 4 实际上,我们可以根据给的输入数据里面的砝码重量做循环,
 5 因为砝码重量总是小于等于1000的,所以可以进行一定程度的优化
 6 
 7 
 8 */
 9 
10 #include <iostream>
11 using namespace std;
12 int f[1005]={0};
13 int main(){
14     
15     int n[7];
16     int weight=0;
17     int a[7]={0,1,2,3,5,10,20};
18     for(int i=1;i<=6;i++){
19         cin>>n[i];
20         weight+=n[i]*a[i];
21     }
22     //2、初始化动态规划数组,做动态规划
23     f[0]=1;
24     for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
25         for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
26             for(int j=weight;j>=a[i];j--){
27                 f[j]=f[j] + f[j-a[i]];
28             }
29         }
30     }
31     //3、统计方案总数
32     int count=0;
33     for(int i=1;i<=weight;i++){
34         if(f[i]) count++;
35     }
36     cout<<"Total="<<count<<endl;
37     return 0;
38 }

 

 

8、bitset优化

 1 /*
 2 
 3 C++的 bitset 在 bitset 头文件中,它是一种类似数组的结构,
 4 它的每一个元素只能是0或1,每个元素仅用1bit空间
 5 
 6 001000010010001000100100010001
 7 
 8 
 9 如果选择状态:
10 f[i][j]表示前i件物品中总重量为j方案的方案是否存在
11 
12 那么f[i][j]里面存的数据也就是0和1,所以可以用bitset来代替
13 
14 二维的状态转移方程空间优化之后
15 f[i][j]=f[i-1][j] || f[i-1][j-w[i]];
16 就变成一维的
17 f[j]=[j] || [j-w[i]];
18 
19 解决j-w[i]>=0之后
20 f[j+a[i]]=f[j+a[i]] || f[j];
21 空间优化之后,也可以写成
22 if(f[j]) f[j+a[i]]=1;
23 
24 
25 这个时候,f[]这个数组就可以用bitset来代替了
26 
27 
28 0000000000000000000000000000001
29 
30 注意:
31 for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
32     for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
33         for(int j=weight;j>=a[i];j--){
34             f[j]=f[j] || f[j-a[i]];
35         }
36     }
37 }
38 使用bitset优化砝码称重的多重背包解法的时候,注意点是什么
39 要解决bitset去掉的那层循环的限制条件,也就是第三层循环中的j>=a[i],
40 可以把f[j]=f[j]||f[j-a[i]]中j的位置都加上a[i]即可解决
41 
42 在砝码称重的bitset的优化中,bitset的左移和右移操作如何确定
43 由砝码称重这题对应的关系,if(f[j]) f[j+a[i]]=1,由f[j]得到f[j+a[i]],那么肯定是左移
44 
45 */
46 
47 
48 #include <iostream>
49 #include <bitset>
50 using namespace std;
51 bitset<1005> f;
52 int main(){
53     
54     int n[7];
55     int a[7]={0,1,2,3,5,10,20};
56     for(int i=1;i<=6;i++){
57         cin>>n[i];
58     }
59     //2、初始化动态规划数组,做动态规划
60     f[0]=1;
61     for(int i=1;i<=6;i++){ //对不同型号的砝码进行循环
62         for(int k=1;k<=n[i];k++){ //对同一个型号的多个砝码进行循环
63             f=f | f<<a[i];
64         }
65     }
66     //3、统计方案总数
67 
68     cout<<"Total="<<(f.count()-1)<<endl;
69     return 0;
70 }

 

 

 

 

 

 

 

 

 

 

 

 
posted @ 2020-06-01 08:02  范仁义  阅读(1542)  评论(0编辑  收藏  举报