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 }

     
  • 相关阅读:
    inotify事件监控
    NFS网络文件共享服务
    Rsync数据同步服务
    SSH连接原理及ssh-key讲解
    C语言I博客作业04
    C语言l博客作业03
    C语言I博客作业02
    定义一个计算字符串高度的方法
    字典转模型
    UIScrollView和UIPageControl
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/12996302.html
Copyright © 2011-2022 走看看