蓝桥杯算法训练 无聊的逗-详细注释版

试题 算法训练 无聊的逗

资源限制

时间限制:1.0s 内存限制:256.0MB

问题描述

逗志芃在干了很多事情后终于闲下来了,然后就陷入了深深的无聊中。不过他想到了一个游戏来使他更无聊。他拿出n个木棍,然后选出其中一些粘成一根长的,然后再选一些粘成另一个长的,他想知道在两根一样长的情况下长度最长是多少。

输入格式

第一行一个数n,表示n个棍子。第二行n个数,每个数表示一根棍子的长度。

输出格式

一个数,最大的长度。

样例输入

4
1 2 3 1

样例输出

3

数据规模和约定

n < = 15 n<=15 n<=15

题解

思路

在这里我们最容易想到的解法就是枚举每一种情况来进行暴力求解,比较数据本身范围也不大

但是现在的难点在于如何表示某一种选木棒的情况,这时候就可以用二进制来进行存储了。

相信大部分同学的教务系统是有查询空教室的功能的,而这个查询空教室是可以选择任意周-星期-节次的任何排列的,他的原理就是用到了二进制

我们可以将一个数转化为二进制,而他二进制的每一位的1表示选,0表示不选,就比如说在这道题中,假设有四个木棍,我们选择第一个和第三个木棍,那么我们就可以将这种选择表示成二进制的0101.

而选择这两个木棍以外的其他所有木棍的情况就可以表示成1111-0101=1010

其次,我们想要判断一种选择的组合是否选择了某一个木棍a,我们只需要将这个木棍单独取出作为一种选择情况 1 < < ( a − 1 ) 1<<(a-1) 1<<a1,比如我选择了第二个木棍,那就是 1 < < ( 2 − 1 ) 1<<(2-1) 1<<(21)=0010.接着,我们将这种情况和要判断的选择组合进行按位与操作,如果结果为0说明当前选择没有选到这个木棍.

比如,1000这种选择情况,我们需要判断它是否选择了第二个木棍,那么我们只需要进行 1000 & 0010 1000\&0010 1000&0010这个运算,可以得出 0000 0000 0000=0,这时候我们就知道这种选择情况并不包含第二个木棍。

反之,1010这种情况, 1010 & 0010 = 0010 = 2 ≠ 0 1010\&0010=0010=2\not=0 1010&0010=0010=2=0,那这种选择情况是选择了第二个木棍的

接着再来说说,移位运算,比如1=0001,当他进行移位操作时,向左移两位,也就是 1 < < 2 1<<2 1<<2,得出来的结果就是 000100 = 0100 000100=0100 000100=0100也就是相当于 1 ∗ 2 2 1*2^2 122,但是使用移位运算可以大大降低时间复杂度

还有,在下面一个代码中比较难理解的地方,就是对于两个木棍的序列,为什么第二个序列的排列情况需要跟第一个序列进行按位与操作,这个倒也不难解释

首先,假设我们序列1选择了2,3两个木棍,也就是00110,但是我们序列2枚举的是3,4,5三个木棍(11100),我们会发现存在一个木棍3重复使用了,这种情况肯定是不存在的,所以我们肯定要把第二个序列的木棍3去掉,想去掉,我们写循环加判断明显是太麻烦了,而且时间复杂度很高,所以我们可以使用按位与运算:假设我们现在2,3这两个棍子已经拿走,那么序列2只能选择(11001)这里面的木棍,我们将“可供选择的木棍列表”和“当前枚举的木棍序列2”进行按位与: 11001 & 11100 = 11000 11001\&11100 = 11000 11001&11100=11000,神奇不?是不是把多与的木棍3去掉了?

这个原理比较好理解,我们可以理解为,对于每一个木棍(也就是二进制的每一位),如果我当前有剩下的,并且我选到了他,那么我就把他选下,如果没有,我就不选他。

注释很全,多揣摩揣摩吧~

代码

package com.lanqiao;

import java.util.Scanner;

/**
 * @author 王宇哲
 */
public class 算法训练_无聊的逗 {

    static int n;
    static int[] nums;

    /**
     * 最长的相同木棍长度
     */
    static int maxLength = Integer.MIN_VALUE;

    /**
     * 存储某种排列组合下木棍的长度
     */
    static int[] length;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        n = scanner.nextInt();
        nums = new int[n];

        //对于n个木棍,共有2^n种排列组合方式
        length = new int[1<<n];

        for(int i=0;i<n;i++){
            nums[i] = scanner.nextInt();

            //1<
            length[1<<i] = nums[i];
        }

        //遍历存储每一种选择方式的长度,i=1的情况
        for(int i = 0;i < ( 1 << n ); i++){

            //这一层遍历是用来选择木棍的
            for(int j = 0;j < n;j++){

                //如果i这种情况和选择木棍j有冲突,也就是第i种选择方式并没有选择第j跟木棍
                //举个例子,比如i为4,二进制就是100,但是j为0,也就是选择了第1根木棍
                //此时 1<
                //因此这种情况出现冲突,不用进行运算
                if((i & (1<<j) ) == 0){
                    continue;
                }

                //如果这种情况考虑在内了,那么第i种情况的长度就是木棍j的长度加上情况i不选择木棍j的长度
                //还是举个例子
                //比如目前i是5,也就是101,j是2,也就是第三根木棍,此时1<
                //那么第i种组合的情况其实就是i = 001 时的长度加上木棍j的长度
                length[i] = length[i - (1<<j)] + nums[j];

                //这个break是用来防止重复计算的
                break;
            }
        }

        //枚举每一种情况i
        for(int i=0;i< (1 << n);i++){
            //j就是选出了所有情况i没有选择的木棍的情况,(1<
            //1<
            //1<
            //(1<
            //那么(1<
            int j = (1 << n) -1 -i;

            //接下来的循环是尝试从选择了全部i没选的木棍的情况开始枚举
            //枚举每一种可能的选择情况,k--会存在不合理的数据,比如说现在剩下的木棍是11000,而k当前枚举到的实是10000,
            //那么此时k-- = 01111,后面会多出三个木棍,所以我们需要使用按位与来去除多余的木棍,
            for(int k = j;k > 0;k = (k-1)&j){
                if(length[k] == length[i]){
                    maxLength = Math.max(maxLength,length[k]);
                }
            }
        }
        System.out.println(maxLength);

    }

}

你可能感兴趣的:(算法,蓝桥杯)