美团笔试 美团笔试题 0816
笔试时间:2025年8月16日
往年笔试合集:
第一题:组数制进二
小美有一个长度为 n 的数组 {a_1,a_2,...,a_n},他希望构造一个非负整数 x,满足 x 的二进制位数不超过数组中最大值的二进制位数(特别的 0 二进制位数为 1 )。 随后,可对数组 a 重复进行以下操作,以使所有元素的总和最大:
选择一个下标 i,同时将 a_i 修改为 a_i or x,将 x 修改为 a_i and x。 在使元素总和达到最大值的前提下,要求所有操作前初始的 x 尽可能小。请输出最大总和及对应的最小 x。
按位或:or 表示按位或运算,即对两个整数的二进制表示的每一位进行逻辑或操作。 按位与:and 表示按位与运算,即对两个整数的二进制表示的每一位进行逻辑与操作。
输入描述
每个测试文件均包含多组测试数据。 第一行输入一个整数 T,代表数据组数;
对于每组测试数据,输入如下: 第一行输入一个整数 n,表示数组的长度;
第二行输入 n 个整数 a_1,a_2,表示数组 a 的元素。
输出描述
对于每组测试数据,新起一行。输出两个整数,用空格分隔:第一个整数为数组可以达到的最大总和;第二个整数为在达到最大总和的前提下初始最小的 x。
样例输入
2
2
3 3
3
1 2 3
样例输出
6 0
9 3
参考题解
这个问题的关键在于理解重复执行操作后,数组 a 和整数 x 的最终状态会是什么。操作如下:a_i = a_i or xx = a_i and x考虑系统中所有数字(a_1, a_2, ..., a_n 和 x)的总和。有一个重要的位运算性质:对于任意两个整数 p 和 q,它们的和等于它们按位或与按位与的和,即 p + q = (p | q) + (p & q)。当我们在 a_i 和 x 上执行操作时,它们的新值是 a_i' = a_i | x 和 x' = a_i & x。根据上述性质,a_i' + x' = a_i + x。这意味着每次操作,a_i 和 x 的总和保持不变。由于其他数组元素 a_j (j ≠ i) 不受影响,因此整个系统 (a_1 + a_2 + ... + a_n + x) 的总和是一个不变量。我们的目标是最大化最终的数组总和 sum(a_final)。 因为系统总和不变,所以: sum(a_final) + x_final = sum(a_initial) + x_initial要使 sum(a_final) 最大化,我们必须让 x_final 尽可能小。现在的问题是,x_final 的最小值是多少?让我们分析单个比特位(例如第 k 位)的行为。如果 a_i 的第 k 位是 0,而 x 的第 k 位是 1,操作后 a_i 的第 k 位变为 1,x 的第 k 位变为 0。这个比特从 x 移动到了 a_i。如果 a_i 的第 k 位是 1,而 x 的第 k 位是 1,操作后 a_i 和 x 的第 k 位都保持为 1。这个比特无法从 x 中移除。由此我们得到一个关键结论:x 中某个位置的比特 k,只有在与一个 a_i 进行操作,而这个 a_i 在 k 位上为 0 时,才能从 x 中被“清除”(移出)。如果对于某个比特位 k,数组中所有的元素 a_i 在该位上都是 1,那么一旦 x 在该位上也是 1,这个比特将永远“卡”在 x 中,无法被清除。确定最终的 x: 令 AND_a = a_1 & a_2 & ... & a_n。这个数代表了数组 a 中所有元素共同拥有的比特位。如果初始 x 在某个位上是 1,并且 AND_a 在这个位上也是 1,那么最终的 x_final 在该位上必然是 1。在其他所有位上,我们都可以通过选择一个合适的 a_i (在该位上为0) 来将 x 的对应位清零。因此,x_final = AND_a & x_initial。确定最大和的表达式: 将 x_final 代入不变量公式: sum(a_final) = sum(a_initial) + x_initial - x_final sum(a_final) = sum(a_initial) + x_initial - (AND_a & x_initial)利用 p - (p & q) = p & (~q),我们可以简化表达式:sum(a_final) = sum(a_initial) + (x_initial & (~AND_a))寻找最优的 x:我们的目标是选择一个初始 x (x_initial) 来最大化 sum(a_final),同时在总和最大的前提下让 x 尽可能小。为了最大化 sum(a_final) = sum(a_initial) + (x_initial & (~AND_a)),我们需要最大化 (x_initial & (~AND_a)) 这一项。x 受到约束:它的二进制位数不能超过数组最大值 max(a) 的二进制位数。设 max_bits = max(a).bit_length()(特别地,当 max(a) = 0 时,位数为1),这意味着 x < 2^max_bits。所有小于 2^max_bits 的数可以用一个掩码 mask = (1 << max_bits) - 1 来表示。那么,要最大化 x & (~AND_a),我们应该让 x 包含 ~AND_a 中尽可能多的比特位,同时受 mask 的限制。最优的选择就是 x = (~AND_a) & mask。这个 x 不仅使 (x & (~AND_a)) 达到最大值,它本身也是所有能达到这个最大值的 x 中最小的一个。因为任何更小的 x 都会缺少一些必要的比特位,导致 (x & (~AND_a)) 的值变小。
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); for (int i = 0; i < n; ++i) cin >> a[i]; long long sum_a = 0; for (int x : a) sum_a += x; if (n == 0) { // 与原 Python 行为一致的保护(虽然通常 n >= 1) cout << 0 << ' ' << 0 << "\n"; continue; } int and_a = (1 << 30) - 1; // 覆盖所有可能位 (a[i] < 2^30) for (int x : a) and_a &= x; int max_val = 0; if (!a.empty()) max_val = *max_element(a.begin(), a.end()); int max_bits = (max_val == 0) ? 1 : (32 - __builtin_clz(max_val)); // bit_length long long mask = (1LL << max_bits) - 1; // 用 64 位承接 long long min_x = (~and_a) & mask; // 两数补与掩码 long long max_sum = sum_a + min_x; cout << max_sum << ' ' << min_x << "\n"; } return 0; }
Java:
import java.io.*; import java.util.*; public class Main { // 简单快速输入 static final class FastScanner { private final InputStream in; private final byte[] buffer = new byte[1 << 16]; private int ptr = 0, len = 0; FastScanner(InputStream is) { this.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 <= 32 && c != -1); if (c == '-') { sgn = -1; c = read(); } while (c > 32 && c != -1) { 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 out = new StringBuilder(); int T = fs.nextInt(); for (int tc = 0; tc < T; tc++) { int n = fs.nextInt(); int[] a = new int[Math.max(n, 0)]; for (int i = 0; i < n; i++) a[i] = fs.nextInt(); long sumA = 0L; for (int x : a) sumA += x; if (n == 0) { out.append("0 0\n"); continue; } int andA = (1 << 30) - 1; // 覆盖所有可能位 (a[i] < 2^30) for (int x : a) andA &= x; int maxVal = 0; for (int x : a) if (x > maxVal) maxVal = x; int maxBits = (maxVal == 0) ? 1 : (32 - Integer.numberOfLeadingZeros(maxVal)); int mask = (1 << maxBits) - 1; // maxBits ≤ 30,int 安全 int minX = (~andA) & mask; // 与 Python: (~and_a) & mask 一致 long maxSum = sumA + (minX & 0xffffffffL) - 0L; // minX 作为非负整型加到 long out.append(maxSum).append(' ').append(minX).append('\n'); } System.out.print(out.toString()); } }
Python:
import sys def solve(): n = int(input()) a = list(map(int, input().split())) # 1. 计算初始总和与按位与 sum_a = sum(a) if n == 0: # 边界情况处理,虽然题目约束 n >= 1 print(0, 0) return # Python的 ~-1 是 -2, 而不是全1。为了安全,用一个足够大的数来初始化与操作的起始值 # 2^30 - 1 < 2^30, a_i < 2^30, 所以 (1<<30) - 1 包含所有可能的位 and_a = (1 << 30) - 1 for x in a: and_a &= x # 2. 找到最大值 max_val = 0 if a: max_val = max(a) # 3. 确定x的最大位数 # 特别的 0 二进制位数为 1 if max_val == 0: max_bits = 1 else: max_bits = max_val.bit_length() # 4. 创建掩码 mask = (1 << max_bits) - 1 # 5. 计算最小的最优x # ~and_a 在Python中是负数,直接与正数mask进行&操作即可得到正确结果 min_x = (~and_a) & mask # 6. 计算最大总和 # 根据推导,增加的和就是 (min_x & (~and_a)),由于min_x的定义,这等于min_x max_sum = sum_a + min_x print(max_sum, min_x) def main(): T = int(input()) for _ in range(T): solve() if __name__ == "__main__": main()
第二题:数组同构
定义变换函数:将一个正整数 x 用其二进制表示中 1 的个数替换,记作 g(x)(即 popcount); 给定两个长度均为 n 的正整数数组 A 和 B; 你可以对 A 或 B 中的任意元素反复执行以下操作,每次操作计数 1:将该元素 x 替换为 g(x); 当且仅当存在置换 \pi,使得对所有 1\leqq i\leqq n 都有 A_i = B_{\pi(i)},也就是两个数组都排序后完全相同,我们称 A 与 B 同构。请计算使 A 与 B 同构所需的最少操作次数。 可以证明题目一定有解。
输入描述
第一行输入一个整数 t,表示测试用例数;
每个测试用例输入格式如下:
第一行输入一个整数 n;
第二行输入 n 个整数 A_1, A_2,;
第三行输入 n 个整数 B_1, B_2
输出描述
对于每个测试用例,输出一行整数——使 A 与 B 同构的最少操作次数。
样例输入
2
3
4 1 2
2 2 1
3
7 3 5
3 3 5
样例输出
2
1
参考题解
C++:
#include <bits/stdc++.h> using namespace std; static const int MAX_SMALL = 60; int popcnt_ll(long long x) { return __builtin_popcountll((unsigned long long)x); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; if (!(cin >> t)) return 0; while (t--) { int n; cin >> n; vector<long long> A(n), B(n); for (int i = 0; i < n; ++i) cin >> A[i]; for (int i = 0; i < n; ++i) cin >> B[i]; // Counter(A), Counter(B) unordered_map<long long, long long> ca, cb; ca.reserve(n * 2 + 3); cb.reserve(n * 2 + 3); for (auto x : A) ++ca[x]; for (auto x : B) ++cb[x]; vector<long long> extraA(MAX_SMALL + 1, 0), extraB(MAX_SMALL + 1, 0); long long total_cost = 0; // all_keys = keys of ca ∪ keys of cb unordered_set<long long> keys; keys.reserve(ca.size() + cb.size()); for (auto &p : ca) keys.insert(p.first); for (auto &p : cb) keys.insert(p.first); f
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
2025打怪升级记录,大厂笔试合集 C++, Java, Python等多种语言做法集合指南