#P3388. 第1题-非负整数数组
-
1000ms
Tried: 93
Accepted: 38
Difficulty: 6
所属公司 :
美团
时间 :2025年8月16日-算法岗
-
算法标签>位运算
第1题-非负整数数组
题解
-
结论
- 设数组为 a1,…,an,最大值为 M=max(ai),其二进制长度为 L(特别地,当 M=0 时 L=1)。
- 记按位与为 A=⋀i=1nai,定义掩码 mask=2L−1。
- 使总和最大的最小初始 x 为:

- 数组可达到的最大总和为:Smax=(∑i=1nai)+x。
-
理由(按位独立,操作等价于“把 x 的某些 1 位转移到某个 ai 上”)
- 考察某一比特位 k:若所有 ai 的该位均为 1(即该位在 A 中为 1),则无法再提升这一位的数组总和;否则,若初始令 x 的该位为 1,总和至多提升 2k,且一定能通过在某次操作选择一个该位为 0 的下标来实现这 2k 的提升。
- 每一位最多提升一次,因此最大可增量正是所有“并非全为 1 的位”的位权之和,等于 mask⊕A 的数值。
- 要达到这一上界,初始应令这些位都在 x 中为 1;反之若少选任一位,则对应的 2k 无法被创造出来,总和达不到最大。因此在达到最大总和的前提下,最小的 x 唯一且为 x=mask⊕A。
- 位长约束通过 mask=2L−1 保证仅使用最低 L 位(当 M=0 时 L=1)。
C++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
if (!(cin >> T)) return 0;
while (T--) {
int n;
cin >> n;
vector<int> a(n);
long long sum = 0;
int mx = 0;
for (int i = 0; i < n; ++i) {
cin >> a[i];
sum += a[i]; // 累加数组和
mx = max(mx, a[i]); // 记录最大值
}
// 计算二进制位长 L(当 mx=0 时,L=1)
int L = (mx == 0) ? 1 : (32 - __builtin_clz(mx));
long long mask = (L == 63 ? (LLONG_MAX) : ((1LL << L) - 1)); // 这里 L<=30,安全
// 按位与 A
int A = (1 << 30) - 1; // 30 位全 1
for (int v : a) A &= v;
long long x = (mask ^ (A & (int)mask)); // x = mask XOR A(仅保留低 L 位)
long long ans = sum + x; // 最大和 = 原和 + x(x 的数值等于能增加的总位权)
cout << ans << ' ' << x << '\n';
}
return 0;
}
Python
import sys
def bit_length_nonzero(x: int) -> int:
# 返回正整数的二进制位长;x=0 时外部特殊处理
return x.bit_length()
data = sys.stdin.read().strip().split()
it = iter(data)
T = int(next(it))
out_lines = []
for _ in range(T):
n = int(next(it))
a = [int(next(it)) for _ in range(n)]
s = sum(a) # 数组和
mx = max(a) if a else 0
L = 1 if mx == 0 else bit_length_nonzero(mx)
mask = (1 << L) - 1 # 仅低 L 位
A = (1 << 30) - 1
for v in a:
A &= v
x = mask ^ (A & mask) # x = (~A)&mask 等价于 mask XOR A 的低 L 位
ans = s + x
out_lines.append(f"{ans} {x}")
sys.stdout.write("\n".join(out_lines))
Java
import java.io.*;
import java.util.*;
public class Main {
static class FastScanner {
private final InputStream in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
FastScanner(InputStream is) { in = is; }
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
int nextInt() throws IOException {
int c, sgn = 1, x = 0;
do { c = read(); } while (c <= ' ' && c != -1);
if (c == '-') { sgn = -1; c = read(); }
while (c > ' ') {
x = x * 10 + (c - '0');
c = read();
}
return x * sgn;
}
}
public static void main(String[] args) throws Exception {
FastScanner fs = new FastScanner(System.in);
StringBuilder sb = new StringBuilder();
int T;
try { T = fs.nextInt(); } catch (Exception e) { return; }
while (T-- > 0) {
int n = fs.nextInt();
int[] a = new int[n];
long sum = 0;
int mx = 0;
for (int i = 0; i < n; i++) {
a[i] = fs.nextInt();
sum += a[i]; // 累加数组和
if (a[i] > mx) mx = a[i]; // 最大值
}
// 计算二进制位长 L(当 mx=0 时 L=1)
int L = (mx == 0) ? 1 : (32 - Integer.numberOfLeadingZeros(mx));
long mask = (1L << L) - 1; // 仅低 L 位
int A = (1 << 30) - 1; // 30 位全 1
for (int v : a) A &= v; // 按位与
long x = mask ^ (A & (int)mask); // x = (~A)&mask 等价形式
long ans = sum + x; // 最大和
sb.append(ans).append(' ').append(x).append('\n');
}
System.out.print(sb.toString());
}
}
题目内容
小美有一个长度为 n 的数组 {a1,a2,...,an} ,他希望构造一个非负整数 x , 满足 x 的二进制位数不超过数组中最大值的二进制位数(特别的 0 二进制位数为 1 )。
随后,可对数组 a 重复进行以下操作,以使所有元素的总和最大:
- 选择一个下标 i ,同时将 ai 修改为 ai or x ,将 x 修改为 ai and x 。
在使元素总和达到最大值的前提下,要求所有操作前初始的 x 尽可能小。请输出最大总和及对应的最小 x 。
按位或:or 表示按位或运算,即对两个整数的二进制表示的每一位进行逻辑或操作。
按位与:and 表示按位与运算,即对两个整数的二进制表的每一位进行逻辑与操作。
输入描述
每个测试文件均包含多组测试数据。
第一行输入一个整数 T(1≦T≦1000) ,代表数据组数;
对于每组测试数据,输入如下:
第一行输入一个整数 n(1≦n≦500) ,表示数组的长度;
第二行输入 n 个整数 a1,a2,...,an(0≦ai<230),表示数组 a 的元素。
输出描述
对于每组测试数据,新起一行,输出两个整数,用空格分隔:第一个整数为数组可以达到的最大总和;第二个整数为在达到最大总和的前提下初始最小的 x 。
样例1
输入
2
2
3 3
3
1 2 3
输出
6 0
9 3