洛谷 B3930 [GESP202312 五级] 烹饪问题 题解
看到各路大神都在用贪心,我就来发一个另类做法吧 (●'◡'●)。
题意描述
给定一个数列 \(\{a_i\}_{i=1}^n\),求找到一组 \(i,j~(i\neq j)\),使 \(a_i \& a_j\) 最大。
约定记号
约定最优解的 \(i\) 取值为 \(i_0\),\(j\) 取值为 \(j_0\)。
即当 \(i=i_0,j=j_0\) 时,\(a_i \& a_j\) 最大。
解题思路
我们可以从高到低枚举每一位,逐步缩小寻找最优解的范围。
设当前寻找最优解的范围是 \([l,r]\),即目前可以钦定 \(i_0,j_0\in [l,r]\)。
初始时,\(l=1,r=n\)。
先将所有数升序排序,考虑排序后这位上的情况,分讨一下。
- 这位全部相同
答案与这一位无关,可以直接跳过,\(l,r\) 不用改动。

- 这位上有且仅有一个 \(1\),位于数列末尾
则最优解的这位一定为 \(0\),将末尾的数这一位置 \(0\),重新排序。
因为只修改了末尾一个数,可以 \(O(n)\) 插入排序,而 \(l,r\) 不用改动。

- 这一位上先是一段 \(0\),然后是一段 \(1\)
先二分分界点,设首个这位为 \(1\) 的数是 \(a_s\)。
可以确定 \(i_0,j_0\in [s,r]\),则令 \(l\leftarrow s\),缩小求解区间。

最后,当 \([l,r]\) 范围缩小到两个数,即 \(r=l+1\),则可以确定 \(i_0=l,j_0=r\),求出最优解。
时间复杂度分析
首先需要对所有数排序,复杂度 \(O(n \log n)\)。
后面缩小区间的操作最多进行 \(31\) 次,最坏每次插入排序有 \(O(n)\) 的时间复杂度,则这一步复杂度为 \(O(n)\),常数有亿点点大。
所以总时间复杂度为 \(O(n \log n)\),瓶颈在排序。
Code
注意代码下标是从 \(0\) 开始的。
#include <cstdio>
#include <algorithm>
using namespace std;
char buf[1<<20],*p1,*p2;
int a[1000000];
inline char gc() // 快速读字符
{
return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin))==buf?EOF:*p1++;
}
inline void read(int &x) // 快读
{
int c;
while((c=gc())<'0'||c>'9');
for(x=c^48;(c=gc())>='0'&&c<='9';x=(x<<3)+(x<<1)+(c^48));
}
int main()
{
int n,st,ed;
read(n);
st=0,ed=n-1; // 当前寻找最优解的范围
for(int i=0;i<n;++i)
{
read(a[i]);
}
sort(a,a+n);
for(int bit=1<<30;bit;bit>>=1) // 从高到低按位枚举,bit标识当前位
{
if((a[st]&bit)||!(a[ed]&bit)) // 这位上全部相同,跳过
{ // 即最小的数这位为1,或最大的数这位为0
continue;
}
if((a[ed]&bit)&&!(a[ed-1]&bit)) // 只有最后一个数这位为1
{
int t=a[ed]^=bit,i;
for(i=ed-1;i>=st&&a[i]>t;--i) // 单轮插入排序
{
a[i+1]=a[i];
}
a[i+1]=t;
}
else
{
int l=st,r=ed,mid;
while(l<r) // 二分0段和1段的分界点,找到第一个这位为1的数
{
mid=(l+r)>>1;
if(a[mid]&bit) r=mid;
else l=mid+1;
}
st=l; // 缩小范围
}
if(ed-st<=1) // 范围缩小到2个数以内,找到答案
{
break;
}
}
printf("%d",a[st]&a[ed]);
return 0;
}
完结撒花 ★,°:.☆( ̄▽ ̄)/$:.°★。
Update 2025.6.15:修正错误的图片。
Update 2025.6.16:微调公式。

浙公网安备 33010602011771号