首页 > 试题广场 >

组数制进二

[编程题]组数制进二
  • 热度指数:169 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
\hspace{15pt}小美有一个长度为 n 的数组 \{a_1,a_2,\dots,a_n\},他希望构造一个非负整数 x,满足 x 的二进制位数不超过数组中最大值的二进制位数(特别的 0 二进制位数为 1 )。
\hspace{15pt}随后,可对数组 a 重复进行以下操作,以使所有元素的总和最大:
\hspace{23pt}\bullet\,选择一个下标 i,同时将 a_i 修改为 a_i\operatorname{or} x,将 x 修改为 a_i\operatorname{and} x
\hspace{15pt}在使元素总和达到最大值的前提下,要求所有操作前初始的 x 尽可能小。请输出最大总和及对应的最小 x

\hspace{15pt}按位或\operatorname{or} 表示按位或运算,即对两个整数的二进制表示的每一位进行逻辑或操作。
\hspace{15pt}按位与\operatorname{and} 表示按位与运算,即对两个整数的二进制表示的每一位进行逻辑与操作。

输入描述:
\hspace{15pt}每个测试文件均包含多组测试数据。 
\hspace{15pt}第一行输入一个整数 T\left(1\leqq T\leqq 1000\right),代表数据组数;

\hspace{15pt}对于每组测试数据,输入如下:
\hspace{15pt}第一行输入一个整数 n\left(1\leqq n\leqq 500\right),表示数组的长度;
\hspace{15pt}第二行输入 n 个整数 a_1,a_2,\dots,a_n\left(0\leqq a_i<2^{30}\right),表示数组 a 的元素。


输出描述:
\hspace{15pt}对于每组测试数据,新起一行。输出两个整数,用空格分隔:第一个整数为数组可以达到的最大总和;第二个整数为在达到最大总和的前提下初始最小的 x
示例1

输入

2
2
3 3
3
1 2 3

输出

6 0
9 3

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        int t = in.nextInt();
        while (t-->0) { // 注意 while 处理多个 case
            int n = in.nextInt();
            long[] num = new long[n];
            long sum = 0;
            for(int i=0; i<n; i++){
                num[i] = in.nextLong();
                sum += num[i];
            }
            //
            int[] flag = new int[30];
            int max_j = 1;
            int min_j = 31;
            for(int i=0; i<n; i++){
                long x = num[i];
                int j = 0; 
                while(x>0){
                    if(x%2==0) flag[j] = 1;  //判断每个二进制位上是否存在0
                    x /= 2;
                    j++;
                }
                max_j = max_j<j ? j : max_j;
                min_j = min_j>j ? j : min_j;  // 高位补足
            }
            // 用x去填每个位置上的0
            long cur = 1;
            long x = 0;
            for(int i=0; i<max_j; i++){
                if(i>=min_j || flag[i]==1) x+=cur;  // [min_j,mat_j)范围内必然存在0需要x填充
                cur *= 2;
            }
            sum += x;
            System.out.printf("%d %d\n", sum, x);
        }
    }
}

发表于 今天 12:27:11 回复(0)
未交卷时 点击“保存并提交” 后 显示 "48/51 组用例通过"
但交卷后显示  状态:编译错误

这是什么情况?
/*
When x is larger, sum() is also larger.
=> Find the smallest x in range [0, 0b1...1 where #1 == 0bmax(ai)'s 1s]
=> such that the sum() is at max

Binary Search
search range: x in [1, 0b1...1 where #1s == #0bmax(ai)'s digits]
    given that 0 <= ai < 2^30: #0bmax(ai)'s digits <= 30
search for what: First Occurrence of x such that sum() is at max
    assumption: as x goes up, sum() continues going up to a point and will no longer increase
    => Last Occurrence of x such that sum()_when_x > sum()_when_x-1

Complexity:
TC: O(occurrence BS) * O(determine sum() for each x) = O(30) * O(n) ~ O(n)
*/

import java.util.Scanner;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int numCases = in.nextInt();
        for (int i = 0; i < numCases; i++) {
            int n = in.nextInt();
            int[] arr = new int[n];
            for (int j = 0; j < n; j++) {
                arr[j] = in.nextInt();
            }

            Result result = smallestXLargestSum(arr);
            System.out.println(result.sum + " " + result.x);
        }
    }

    // Helpers
    /* Given an arr[], return the smallest x where 0bx has #digits <= 0blargest ai's #digits
     * such that sum(ai) is the largest.
     */
    private static Result smallestXLargestSum(int[] arr) {
        int largestNumDigits = getLargestNumDigits(arr);
        int largestX = 1;
        for (int i = 1; i < largestNumDigits; i++) { // shifting & filling with 1
            largestX <<= 1;
            largestX |= 1;
        }
        // System.out.println("largestX = " + largestX);
        // At this point, largestX = 0b1...1 (#1s == largestNumDigits)

        // initialize Occurrence BS
        int left = 1;
        int right = largestX;
        long prevSum = getSumWithX(0, arr); // sum() when x = 1
        int midX = 0; // represent x

        while (left < right - 1) {
            midX = left + (right - left) / 2;
            long currSum = getSumWithX(midX, arr);
            if (currSum > prevSum) { // search to the right of mid (inclusive)
                left = midX;
                prevSum = currSum;
            } else { // prevSum >= currSum
                right = midX - 1;
            }
        }

        // post processing
        long sumWithRightX = getSumWithX(right, arr);
        if (right - 1 >= 0 && sumWithRightX > getSumWithX(right - 1, arr)) {
            return new Result(sumWithRightX, right);
        }
        long sumWithLeftX = getSumWithX(left, arr);
        if (left - 1 >= 0 && sumWithLeftX > getSumWithX(left - 1, arr)) {
            return new Result(sumWithLeftX, right);
        }
        else return new Result(getSumWithX(0, arr), 0);
    }

    // Given an x, return the sum() by applying the formulas.
    private static long getSumWithX(int x, int[] arr) {
        long sum = 0;
        for (int num: arr) {
            sum += num | x;
            x = num & x;
        }

        // System.out.println("When x = " + x + ", sum() = " + sum);
        return sum;
    }

    // return the #digits of binary form of the largest ai. Assert return number is in range 1 ~ 30
    private static int getLargestNumDigits(int[] arr) {
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] > max) max = arr[i];
        }

        int numDigits = 0;
        if (max == 0) return 1;

        while (max > 0) {
            numDigits++;
            max >>= 1;
        }
        // System.out.println("for current arr with length = " + arr.length + ", largestNumDigits = " + numDigits);
        return numDigits;
    }
}

class Result {
    long sum;
    int x;

    public Result(long sum, int x) {
        this.sum = sum;
        this.x = x;
    }
}


发表于 今天 07:34:05 回复(0)
// 结论:x 等于 对于二进制的某个位,任意存在一个元素,元素该位为0,则x对应位置为1。例如 数组元素 二进制 110 101 111 则x应为011
// 证明:
//   首先 基本定理 a1 + x = a1|x(原位值) + a1&x(进位值)
//   对于数组[a1, a2, a3],假设初始x,最终数组和 finalSum = 初始和(a1+a2+a3) + 初始x -finalx(初始x与操作元素&运算而成)
//   假设x 与 a1 a2进行了操作,数组变为[a1|x, a2|(a1&x), a3], x变为finalx=a2&(a1&x);
// 则 最终数组和finalsum + x终值finalx = a3 + a1|x + a2 | (a1&x) + a2 & (a1&x)
// 结合上述基本定理,合并末尾两个加数,进一步得到 finalsum + = a3 + a1|x + a2 + a1&x
// 再进一步得到 finalsum + a2&(a1&x) = a1 + a2 + a3 + x;
//               finalsum = a1 + a2 + a3 + x - a2&a1&x;
// 所求问题转化为:求 max(x - 任选多次操作的数组元素 & x) 这个任选还可以重复选择
// 对于 max(x - 任选多次操作的数组元素 & x) 设标准x 等于 对于二进制的某个位,任意存在一个元素,元素该位为0,则x对应位置为1。因为这种情况下 max(x - 任选多次操作的数组元素 & x) 的减数最终可以被置为0
// 对于一个确定的数组这个标准x也是唯一确定的,例如 数组元素 二进制 110 101 111 则标准x应为011
// 反例证明为什么标准x能使得表达式值最大:
// 假设x比标准x大,即某些标准x中为0的位置为1,但是最后相减,被减数和减数此位置的数都为1,减出来的差值与标准x一样。假设x比标准x小,推理类上,减出来的差值比标准x还小。
// 所以,使得和最大的最小x为标准x,即:x 等于 对于二进制的某个位,任意存在一个元素,元素该位为0,则x对应位置为1。例如 数组元素 二进制 110 101 111 则x应为011

//代码 直接计算这个x
public class MaxSumAndX { // 计算数字的二进制位数(0的位数为1 private static int getBitLength(int num) { if (num == 0) { return 1;  } int bits = 0;  while (num > 0) {
            bits++;  num >>= 1;  } return bits;  } public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);  // 输入数组(示例输入)  System.out.println("请输入数组元素(以空格分隔):");  String[] parts = scanner.nextLine().split(" ");  int[] a = new int[parts.length];  for (int i = 0; i < parts.length; i++) {
            a[i] = Integer.parseInt(parts[i]);  } // 步骤1:计算初始总和S  int S = 0;  for (int num : a) {
            S += num;  } // 步骤2:计算数组最大值及其二进制位数b  int maxA = a[0];  for (int num : a) { if (num > maxA) {
                maxA = num;  }
        } int b = getBitLength(maxA);   // 步骤3:计算mask(第k位为1表示所有元素第k位都是1 int mask = 0;  for (int k = 0; k < b; k++) { boolean allOne = true;  for (int num : a) { if ((num >> k & 1) != 1) { // k位不是1  allOne = false;  break;  }
            } if (allOne) {
                mask |= 1 << k; // k位置为1  }
        } // 步骤4:构造最小x(包含所有可清除位)  int x = 0;  for (int k = 0; k < b; k++) { if ((mask >> k & 1) == 0) { // k位是可清除位  x |= 1 << k;  }
        } // 最大总和 = S + x  int maxSum = S + x;   // 输出结果  System.out.println("最大总和:" + maxSum);  System.out.println("对应的最小x" + x);  }
}



发表于 2025-08-22 14:52:08 回复(0)