题解:洛谷 B3930 烹饪问题
【题目来源】
洛谷:B3930 [GESP202312 五级] 烹饪问题 - 洛谷
【题目描述】
有 \(N\) 种食材,编号从 \(1\) 至 \(N\),其中第 \(i\) 种食材的美味度为 \(a_i\)。
不同食材之间的组合可能产生奇妙的化学反应。具体来说,如果两种食材的美味度分别为 \(x\) 和 \(y\) ,那么它们的契合度为 $x\ \text{and}\ y $。
其中,\(\text{and}\) 运算为按位与运算,需要先将两个运算数转换为二进制,然后在高位补足,再逐位进行与运算。例如,\(12\) 与 \(6\) 的二进制表示分别为 \(1100\) 和 \(0110\) ,将它们逐位进行与运算,得到 \(0100\) ,转换为十进制得到 4,因此 \(12\ \text{and}\ 6 = 4\)。在 C++ 或 Python 中,可以直接使用 & 运算符表示与运算。
现在,请你找到契合度最高的两种食材,并输出它们的契合度。
【输入】
第一行一个整数 \(N\),表示食材的种数。
接下来一行 \(N\) 个用空格隔开的整数,依次为 \(a_1,\cdots,a_N\),表示各种食材的美味度。
【输出】
输出一行一个整数,表示最高的契合度。
【输入样例】
3
1 2 3
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 个整数 \(a_1, a_2, \ldots, a_N\),求 \(\max_{i \neq j}(a_i \ \& \ a_j)\),即两两按位与的最大值。这是一个按位贪心 + 排序剪枝问题,关键在于利用按位与的性质:若 \(a_i \ \& \ a_j = v\),则 \(a_i \geq v\) 且 \(a_j \geq v\)。
-
算法选择:
- 降序排序 + 剪枝:将数组从大到小排序,外层枚举 \(a_i\),若 \(a_i \leq\) 当前最大值则剪枝(因为后续元素更小,与运算结果不可能更大)
- 内层枚举:与 \(a_i\) 后面的元素进行与运算,更新最大值
-
关键步骤:
- 读入数据:\(N\) 和数组 \(a[1..N]\)
- 降序排序:
sort(a+1, a+n+1, greater<int>()) - 优化枚举(\(i\) 从 \(1\) 到 \(N\)):
- 剪枝:若
a[i] <= maxn,continue(后续元素更小,无法产生更大与值) - 内层 \(j\) 从 \(i+1\) 到 \(N\):
maxn = max(maxn, a[i] & a[j])
- 剪枝:若
- 输出
maxn
-
时间/空间复杂度:
- 时间复杂度:\(O(N \log N + N^2)\),排序 \(O(N \log N)\),最坏情况仍 \(O(N^2)\),但剪枝大幅减少实际运算
- 空间复杂度:\(O(N)\),存储数组
-
按位与贪心剪枝的核心思想:
- 与运算的单调性:\(a_i \ \& \ a_j \leq \min(a_i, a_j)\)。因此若 \(a_i \leq\) 当前最大与值,则 \(a_i\) 与任何数的与值 \(\leq a_i \leq\) 当前最大值,无需继续枚举
- 降序排序的优势:大数在前,更容易快速找到较大的与值,使
maxn尽早变大,从而触发更多剪枝 - 高位优先效应:按位与的结果高位为1比低位为1更重要,降序排序使高位为1的数优先配对
- 适用于"最大按位与/或"问题,核心在于利用与运算的上界性质进行剪枝优化
【算法标签】
普及 #贪心
【代码详解】
// 40分版本
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005; // 最大数组长度
int n; // 数组元素个数
int maxn = -1e9; // 最大与运算结果,初始化为极小值
int a[N]; // 存储输入数组
int main()
{
// 输入数组大小
cin >> n;
// 输入数组元素
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 暴力枚举所有不相同的元素对
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
// 跳过相同的元素对
if (i == j)
{
continue;
}
// 计算按位与并更新最大值
maxn = max(maxn, a[i] & a[j]);
}
}
// 输出最大的按位与结果
cout << maxn << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005; // 最大数组长度
int n; // 数组元素个数
int maxn = -1e9; // 最大与运算结果,初始化为极小值
int a[N]; // 存储输入数组
int main()
{
// 输入数组大小
cin >> n;
// 输入数组元素
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 将数组从大到小排序
sort(a + 1, a + n + 1, greater<int>());
// 优化后的双重循环
for (int i = 1; i <= n; i++)
{
// 剪枝:如果当前元素已经小于等于当前最大值,跳过
if (a[i] <= maxn)
{
continue;
}
// 与后面的元素进行按位与运算
for (int j = i + 1; j <= n; j++)
{
maxn = max(maxn, a[i] & a[j]);
}
}
// 输出最大的按位与结果
cout << maxn << endl;
return 0;
}
【运行结果】
3
1 2 3
2
浙公网安备 33010602011771号