状态压缩DP--洛谷P1441砝码称重--java实现

题目链接:

P1441 砝码称重 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目大意:

给定n个砝码,不同重量,可以重复。去掉其中m个后,能够称重多少种类的重量。

解析:

  • 整体

    我们枚举每一种砝码的排列方法,根据题目条件:去掉m个后,这个标准,过滤出能够使用的排列方法。

    根据这些排列方法,计算出最终能够称多少种类的重量。

  • n个砝码找出符合去掉m个条件的排列方式

    n个砝码的组合数,容易看出是

    \[2^n -1 \]

    种.

    所以可以枚举这些数字。

    计算出它们二进制中1的个数cnt,如果n-cnt==m,说明符合条件。

  • 关于如何计算二进制中1的个数

    这里给出一种方法。

    对于一个二进制数a,对它进行减一后,从右往左看第一个出现的1会变为0.

    所以循环使用a=(a&(a-1))的方式,直到a为0.每次循环,计数自增。

    最终计数的数量就是1的数量。

    代码:

    private static int getOneCnt(int num){
            int cnt = 0;
            while (num!=0){
                cnt++;
                num = num&(num-1);
            }
            return cnt;
        }
    
  • 计算当前排列下,一共有多少种称重方式

    这里采用二进制的方式表示能够称哪一种重量。

    • 称重举例

      按照题目测试用例:

      三个砝码1,2,2,去除1个。

      假设去除2。

      剩下两个砝码1,2.

      它们可以组成三种重量:1,2,3.

      所以我们可以用三位二进制表示:

      从右往左:

      第一位1:代表能够称重1的重量

      第二位1:代表能够称重2的重量

      第三位1:代表能够称重3的重量

    • 一般情况

      假设一共能够称重的最大数量为n,那么使用n位二进制。某一位是1,代表从右往左数的这一位能够被测量出来。

      所以对于最大数量位n,需要十进制数为

      \[2^n -1 \]

    • 如何表示所有的重量
      • 表示称重当前重量

        我们使用一个数字a=1.表示它最低位是1,为了下面位移使用。

        如果使用了第i个砝码weight[i],可以用a<<weight[i]来表示我们可以测量的重量。

        因为1向左移动了weight[i],所以从右往左的weight[i]位置上是1,表示这个重量能够被测量。

        (如果这段不太理解,可以再看一下上一步)

      • 将重量累加

        假设原本记录的重量是oldWeight,增加的重量是weight,增加后的重量是newWeight.

        先给出计算方法:

        newWeight = oldWeight|(oldWeight<<weight)

        • oldWeight<<weight说明

          字面意思是oldWeight左移了weight位。

          结合上面的分析,这个可以理解成,

          对于oldWeight,针对每一种重量,都再增加了weight的重量,作为新的,使用了weight这个砝码后,能够称重的重量。

          如果原本oldWeight[i]是0,表示i索引代表的重量不能够被称重,那么即使进行了左移,也不能被称重。

          如果原本oldWeight[i]是1。增加了weight重量后,能够被称重的重量是oldWeight[i]向左移动weight的位置。

        • oldWeight|(oldWeight<<weight)说明

          将(oldWeight<<weight)记为useWeight。表示使用了weight砝码后能够称重的重量。

          那么如何将原本的重量和现在的useWeight加起来?

          使用按位或运算。这样子得到的结果,就是不适用weight砝码时能够测量的所用重量+使用了weight后能够测量的所有重量。

          或运算:

          有1则1----如果某种重量能够被测量了,就用1表示。

          全0则0---如果oldWeight不能测量这个重量,oldWeight<<weight也不能测量这个重量,那么或运算后,这个重量所代表的二进制位置仍旧位0,不能测量。

          于是:

          newWeight = oldWeight|(oldWeight<<weight)

      至此,分析完毕。

    一点问题:

    题目给定的数据范围:

    \[n≤20 \]

    \[m≤4 \]

    \[m < n \]

    \[a i ​ ≤100 \]

    表示最多20个砝码,每个砝码最多100.所以最多称重100*20=2000.

    按照上面分析,需要的排列数是

    \[2^{100} -1 \]

    java中,long最大是

    \[2^{64} -1 \]

    所以大小远远不够。

    所以我们使用java API中的BitSet来进行二进制的计算。

    主要使用到的方法:

    • bitset.get(i)

      获取第i位的bit数。

    • bitset.set(i,j)

      将第i位的bit置为j

    • bitset.or(anotherBitSet)

      或运算,结果赋值给bitset

    • bitset.cardinality()

      获取当前bitset中,多少位是1.

    java代码:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.BitSet;
    import java.util.StringTokenizer;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            StringTokenizer st = new StringTokenizer(br.readLine());
            int n = Integer.parseInt(st.nextToken());
            int m = Integer.parseInt(st.nextToken());
            int[] weight = new int[n];
            st = new StringTokenizer(br.readLine());
            for(int i = 0;i<n;i++){
                weight[i] = Integer.parseInt(st.nextToken());
            }
            int oneCnt = 0;
            int result = 0;
            for(int i = 0;i<(1<<n);i++){
                oneCnt = getOneCnt(i);
                if(n-oneCnt != m){
                    continue;
                }
                BitSet total = new BitSet();
                total.set(0);
                BitSet temp;
                for(int j = 0;j<n;j++){
                    if((i&(1<<j))==0){
                        continue;
                    }
                    temp = (BitSet) total.clone();
                    temp = moveLeft(temp,weight[j]);
                    total.or(temp);
                }
                result = Math.max(result,total.cardinality());
            }
            //初始化total是1.因为后面需要进行移位操作
            //这一位表示称重0的重量.
            //实际上这个重量不能称出来,所以result-1.
            System.out.println(result-1);
        }
    
        private static int getOneCnt(int num){
            int cnt = 0;
            while (num!=0){
                cnt++;
                num = num&(num-1);
            }
            return cnt;
        }
        private static BitSet moveLeft(BitSet bitset,int offset){
            BitSet result = new BitSet();
            for(int i = 0;i< bitset.length();i++){
                result.set(i+offset,bitset.get(i));
            }
            return result;
        }
    }
    
    
posted @ 2021-06-11 15:24  Monstro  阅读(217)  评论(0)    收藏  举报