题目链接

REAL745 小红的二进制操作

题目描述

小红拿到了一个数组,她可以进行最多两次操作:选择一个元素,使其加1。小红希望操作结束后,数组所有元素乘积的二进制末尾有尽可能多的0。你能帮帮她吗?

思路分析

一个数的二进制末尾有多少个0,取决于其质因数分解中因子2的数量。设这个数量为 。整个数组所有元素乘积的二进制末尾0的数量,就是数组中每个元素 的总和,即

我们的目标是最多使用两次“加1”操作来最大化这个总和。这是一个典型的贪心问题,我们需要让每次操作的“收益”(即 总和的增量)最大化。

我们可以分情况讨论这两次操作的分配方式:

  1. 0次操作: baseline,总和为初始数组的

  2. 1次操作: 我们应该遍历所有元素 ,计算将 变为 带来的增量 。我们选择能使这个增量最大的那个元素进行操作。

  3. 2次操作: 这两次操作可以有两种用法:

    • 作用于两个不同元素:为了总收益最大,我们应该贪心地选择那两个能提供最大单次操作增量的元素。即,我们计算出所有元素的单次操作增量,然后取最大的两个相加。
    • 作用于同一个元素两次:我们将两次操作都用于同一个元素 ,使其变为 。这种情况带来的增量是 。我们需要遍历所有元素,找到这种“二次操作”能带来的最大增量。

最终的答案就是上述所有可能情况(0次操作、1次操作、2次操作于不同元素、2次操作于相同元素)中,能得到的 总和的最大值。

算法步骤

  1. 计算初始的 总和
  2. 对每个元素 ,计算两种潜在的增量:
    • 单次操作增量:
    • 二次操作增量:
  3. 将所有单次操作增量 存入一个列表并降序排序。
  4. 找到所有二次操作增量 中的最大值,记为 max_g2
  5. 最终的最大增量 max_gain 是以下几种情况的最大值:
    • 0 (0次操作)
    • g1[0] (1次操作)
    • g1[0] + g1[1] (2次操作于不同元素)
    • max_g2 (2次操作于相同元素)
  6. 最终答案是

代码

#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <functional>

using namespace std;

// 使用GCC/Clang内置函数计算二进制末尾0的个数,效率很高
int count_trailing_zeros(int n) {
    if (n <= 0) return 0; // __builtin_ctz(0) is undefined
    return __builtin_ctz(n);
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;
    vector<int> a(n);
    long long initial_sum = 0;
    vector<int> gains1;
    vector<int> gains2;

    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        int v_orig = count_trailing_zeros(a[i]);
        initial_sum += v_orig;
        gains1.push_back(count_trailing_zeros(a[i] + 1) - v_orig);
        gains2.push_back(count_trailing_zeros(a[i] + 2) - v_orig);
    }

    long long max_gain = 0;

    sort(gains1.begin(), gains1.end(), greater<int>());

    // 情况1: 使用1次操作
    if (n >= 1) {
        max_gain = max(max_gain, (long long)gains1[0]);
    }

    // 情况2: 使用2次操作
    if (n >= 1) {
        long long max_gain_2_ops = -2e18; // 一个足够小的值
        if (n >= 2) {
            // 2次操作在不同元素上
            max_gain_2_ops = max(max_gain_2_ops, (long long)gains1[0] + gains1[1]);
        }
        
        // 2次操作在相同元素上
        int max_g2 = -2e9;
        for (int g : gains2) {
            max_g2 = max(max_g2, g);
        }
        max_gain_2_ops = max(max_gain_2_ops, (long long)max_g2);
        
        max_gain = max(max_gain, max_gain_2_ops);
    }

    cout << initial_sum + max_gain << endl;

    return 0;
}
import java.util.Scanner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] a = new int[n];
        long initialSum = 0;
        List<Integer> gains1 = new ArrayList<>();
        List<Integer> gains2 = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            a[i] = sc.nextInt();
            int vOrig = Integer.numberOfTrailingZeros(a[i]);
            initialSum += vOrig;
            gains1.add(Integer.numberOfTrailingZeros(a[i] + 1) - vOrig);
            gains2.add(Integer.numberOfTrailingZeros(a[i] + 2) - vOrig);
        }

        long maxGain = 0;

        Collections.sort(gains1, Collections.reverseOrder());

        // 情况1: 使用1次操作
        if (n >= 1) {
            maxGain = Math.max(maxGain, gains1.get(0));
        }

        // 情况2: 使用2次操作
        if (n >= 1) {
            long maxGain2Ops = Long.MIN_VALUE;
            if (n >= 2) {
                // 2次操作在不同元素上
                maxGain2Ops = Math.max(maxGain2Ops, (long)gains1.get(0) + gains1.get(1));
            }

            // 2次操作在相同元素上
            int maxG2 = Integer.MIN_VALUE;
            for (int g : gains2) {
                maxG2 = Math.max(maxG2, g);
            }
            maxGain2Ops = Math.max(maxGain2Ops, maxG2);
            
            maxGain = Math.max(maxGain, maxGain2Ops);
        }

        System.out.println(initialSum + maxGain);
    }
}
import sys

# 辅助函数计算二进制末尾0的个数
def count_trailing_zeros(n):
    if n <= 0:
        return 0
    # (n & -n) 可以分离出最低位的1
    # .bit_length() - 1 可以得到这个1的位置(0-indexed)
    return (n & -n).bit_length() - 1

def solve():
    n = int(sys.stdin.readline())
    a = list(map(int, sys.stdin.readline().split()))

    initial_sum = 0
    gains1 = []
    gains2 = []

    for x in a:
        v_orig = count_trailing_zeros(x)
        initial_sum += v_orig
        gains1.append(count_trailing_zeros(x + 1) - v_orig)
        gains2.append(count_trailing_zeros(x + 2) - v_orig)

    max_gain = 0
    
    gains1.sort(reverse=True)

    # 情况1: 使用1次操作
    if n >= 1:
        max_gain = max(max_gain, gains1[0])

    # 情况2: 使用2次操作
    if n >= 1:
        max_gain_2_ops = -float('inf')
        if n >= 2:
            # 2次操作在不同元素上
            max_gain_2_ops = max(max_gain_2_ops, gains1[0] + gains1[1])
        
        # 2次操作在相同元素上
        max_gain_2_ops = max(max_gain_2_ops, max(gains2))
        
        max_gain = max(max_gain, max_gain_2_ops)

    print(initial_sum + max_gain)

solve()

算法及复杂度

  • 算法:贪心
  • 时间复杂度,其中 是数组的大小。主要的时间开销在于对单次操作增益列表 gains1 的排序。计算初始值和增益列表本身是
  • 空间复杂度,用于存储两种操作的增益列表。