核心思路

注意到异或运算是按位独立的,考虑按位贪心。

对于第 \(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. 先找到所有 \(1\) 的数量大于 \(0\) 的数量的位,让 \(k\) 的这些位为 \(1\)
  2. 然后对剩下的都位进行按位贪心:从高位开始,如果第 \(j\) 位在变 \(1\) 后不超过 \(m\),那么令 \(k\) 的第 \(j\) 位为 \(1\),并加上此次操作的代价,否则为 \(0\)
  3. 如果经过操作 \(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;
}