zoukankan      html  css  js  c++  java
  • 算法与数据结构---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 }

     
  • 相关阅读:
    LeetCode120 Triangle
    LeetCode119 Pascal's Triangle II
    LeetCode118 Pascal's Triangle
    LeetCode115 Distinct Subsequences
    LeetCode114 Flatten Binary Tree to Linked List
    LeetCode113 Path Sum II
    LeetCode112 Path Sum
    LeetCode111 Minimum Depth of Binary Tree
    Windows下搭建PHP开发环境-WEB服务器
    如何发布可用于azure的镜像文件
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/12996302.html
Copyright © 2011-2022 走看看