核心思路
注意到异或运算是按位独立的,考虑按位贪心。
对于第 \(j\) 位来说,如果 \(k\) 的第 \(j\) 位是 \(1\),那么相当于对全部元素的第 \(j\) 位取反;如果 \(k\) 的第 \(j\) 位是 \(0\),那么相当于全部元素的第 \(j\) 位不变。需要在序列和不超过 \(m\) 的情况下,最大化 \(k\)。
我们肯定希望 \(k\) 有更多的位是 \(1\),但是变 \(1\) 是有代价的,那就是会把全部元素的第 \(j\) 位取反。但是这个取反也不一定是坏的,取决于第 \(j\) 位 \(1\) 的数量和 \(0\) 的数量。如果第 \(j\) 位取反前 \(1\) 比 \(0\) 多,那么取反后总和反而会减小。反之,如果第 \(j\) 位取反前 \(0\) 比 \(1\) 多,那么取反后总和会增大。
变 \(0\) 是没有代价的,但同时也没有收获,即总和不变。
对于那些能让总和减少,又能增大 \(k\) 的位,我们一定开心坏了,因为它百利而无一害。为了防止错过这些“好果子”,我们应该把这些“好果子”先吃了再说,也就是先找到所有取反前 \(1\) 比 \(0\) 多的位,然后让 \(k\) 的那一位为 \(1\)。这样做既符合贪心的精神(百利而无一害的先贪),又能保证后续决策的正确性。
对于那些能增大 \(k\),但是会让总和增大的位,我们是开心的,但是有一个隐忧:所有会让总和减小的位都处理完了,剩下的都是会让总和变大的,如果总和超过了 \(m\) 怎么办?根据按位贪心的原则,从高位开始,如果第 \(j\) 位在总和增加后不超过 \(m\),那么可以令 \(k\) 的第 \(j\) 位为 \(1\),并加上此次操作的代价,否则为 \(0\)。
这样做的正确性在于,我已经在让总和增大前通过让总和最小化来最大化我增大的空间,这样就能让 \(k\) 最大了。
还有最后一个问题:什么时候输出 \(-1\)?很简单,当我经过上面这么多的操作,尽最大的努力减少总和,可是它却还是大于 \(m\),我还能干什么呢?我只能输出 \(-1\) 了。
至此整体思路已经梳理完毕:
- 先找到所有 \(1\) 的数量大于 \(0\) 的数量的位,让 \(k\) 的这些位为 \(1\)。
- 然后对剩下的都位进行按位贪心:从高位开始,如果第 \(j\) 位在变 \(1\) 后不超过 \(m\),那么令 \(k\) 的第 \(j\) 位为 \(1\),并加上此次操作的代价,否则为 \(0\)。
- 如果经过操作 \(1\) 和操作 \(2\),总和还是大于 \(m\),输出 \(-1\),否则输出 \(k\)。
复杂度分析
时间复杂度 \(O(\sum n)\)(但有个大常数 \(50\))。空间复杂度 \(O(1)\)(但有个大常数 \(50\))。
代码细节
- \(\log_2 10^{15} \approx 50\),也就是最高位从 \(50\) 开始即可。
- 亲测不用开
__int128。 - 可以预处理 \(2\) 的幂,防止 \(\operatorname{pow}\) 函数带来的精度问题。
- 记得多测清空。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define div() cerr << "-----------------------\n";
#define debug(n) cerr << #n << " = " << n << "\n";
#define deb(n) cerr << #n << " = " << n << " ";
int n,m;int a;int t;int cnt1[105],cnt0[105];int sum;int base[105];int ans[105];
inline void work(int cas){
// 记得多测清空
memset(cnt1,0,sizeof(cnt1));memset(cnt0,0,sizeof(cnt0));
cin >> n >> m;sum = 0;
for(int i = 1;i <= n;i++){
cin >> a;sum += a;
bitset<60>b(a);
for(int j = 0;j <= 50;j++){cnt1[j] += b[j];cnt0[j] += 1-b[j];}
}
for(int j = 0;j <= 50;j++){
// 如果 1 比 0 多就果断选
if(cnt1[j] >= cnt0[j]){
ans[j] = 1;
sum -= (cnt1[j] - cnt0[j])*base[j];
}
// 否则将第 j 位设为 0,因为前面没有初始化
else{
ans[j] = 0;
}
}
for(int j = 50;j >= 0;j--){
// 如果不会超就选
if(cnt1[j] < cnt0[j] && sum + (cnt0[j]-cnt1[j])*base[j] <= m){
ans[j] = 1;
sum += (cnt0[j] - cnt1[j])*base[j];
}
}
// 如果尽最大努力依旧超过m,输出-1
if(sum > m){
cout << "Case #" << cas << ": -1\n";
return;
}
// 计算最终答案fans
int fans = 0;
for(int j = 50;j >= 0;j--){
fans += base[j]*ans[j];
}
cout << "Case #" << cas << ": " << fans << "\n";
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
// 预处理 2 的幂
base[0] = 1;
for(int i = 1;i <= 50;i++){base[i] = base[i-1]*2;}
cin >> t;
for(int i = 1;i <= t;i++){work(i);}
return 0;
}
浙公网安备 33010602011771号