【VJ】Namomo Camp 2 G-Kones xor题解

文章介绍了一种解决非负整数序列异或优化问题的贪心算法,重点在于理解和应用异或操作特性以找到最小贡献的x值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门:Kones xor
标签:贪心

题目大意

给出一个长度为n的非负数序列a1,a2,······,an,以及一个非负整数k,你需要进行一次以下操作:任选一个非负整数x,其满足在二进制下1的个数不超过k,然后将序列a中每个数与x进行异或运算,若运算结果大于原来的值则替换,否则不变。现在问你x要取何值才能使最终序列各项之和最大,若有多个符合条件的x,输出最小的那个。规定ai和x在二进制下固定为m位。
输入:三个数n,m,k(1<=m<=30),以及一个长度为n的非负整数序列a1,a1,······,an
输出:符合条件的最小的x值。

算法分析

  • 由题意可知,不管x取何值,都不会使得序列中元素变小,我们只要保证增量最大即可。考虑异或操作是逐位进行的,我们可以对x的每一位分开算贡献,最后将每一位的最优情况合在一起得到答案。但这种贪心想法会被取max的规定限制,因为如果异或操作的值小于原数,x就相当于对该数无贡献,那么我们就要在贪心的时候将每个数分为有贡献和无贡献两种情况讨论。
  • 但这样一来x的各位之间就有了联系,而非相互独立,那么我们之前逐位取最优的情况似乎就假了。因为你在算x第i位取1时对aj的贡献时,你并不知道x最后对aj是否有贡献,这取决于从i+1位到第m位与aj异或后的值是变大、变小还是不变。但我们会发现这种联系是单向的,是从高位到低位的传递。那我们只要从后往前贪心就行了。
  • 本题到此还没有结束,因为贪心并非像字面上那么简单。异或的结果并不是非大即小的,如果x和aj从第i+1到第m位的每一位都相同,那么第i位是否有贡献就取决于其本身,要是这样写一个dfs复杂度就是230,显然不可行。我们不妨从异或的性质入手,思考每一位异或的特点就会发现:0异或任何数都为本身,1异或任何数都为取反。那么我们只要枚举x最高位的1的位置就能判断序列中哪些数是有贡献的,哪些是无贡献的。因为最高位1的前面的0异或序列中所有数都为本身。
  • 现在算法的复杂度大致为O(nm),但实现上还有一些细节。因为题目要求输出最小的x,我们需要创建一个结构体存两个变量,分别是每一位的总贡献和下标,然后写一个特殊排序规则:优先按贡献降序排序,贡献相同则按下标升序排序。然后我们取排序后的前min(k,y)位,其中y代表最高位1的位数。我们将这些选中的位定为1,其它位定为0。最后还要特判k=0的情况,此时x只有一种可能,我们直接输出0就行了。加上排序后算法的总复杂度为O(nmlogm)。

代码实现

#include <iostream>
using namespace std;
#include <algorithm>
int c[100005],ans=0;
long long sum[45];
struct Item{
	long long x,id;
}I[45];
bool rule(Item a,Item b){
	if(a.x!=b.x)return a.x>b.x;
	return a.id<b.id;
}
int main(){
	int i,j,x,n,m,k,ans1;
	long long ans0=-1e18,mx=0LL;
	cin>>n>>m>>k;
	for(i=1;i<=n;i++)cin>>c[i];
	if(!k){
		cout<<0;
		return 0;
	}
	k--;
	for(i=0;i<m;i++){
		mx=0LL;
		for(j=0;j<m;j++)
			sum[j]=0LL;
		for(j=1;j<=n;j++)
			if(!(c[j]&(1LL<<i))){
				mx+=(1LL<<i);
				for(int j1=0;j1<i;j1++)
					if(c[j]&(1LL<<j1))sum[j1]-=(1LL<<j1);
					else sum[j1]+=(1LL<<j1);
			}
		for(j=0;j<i;j++){
			I[j].x=sum[j];
			I[j].id=j;
		}
		sort(I,I+j,rule);
		for(j=0;j<min(k,i);j++)
			mx+=max(I[j].x,0LL);
		if(mx>ans0){
			ans0=mx;
			ans1=i;
		}
	}
	if(ans0<=0){
		cout<<0;
		return 0;
	}
	i=ans1;
	mx=0LL;
	for(j=0;j<m;j++)
		sum[j]=0;
	for(j=1;j<=n;j++)
		if(!(c[j]&(1LL<<i))){
			mx+=(1LL<<i);
			for(int j1=0;j1<i;j1++)
				if(c[j]&(1LL<<j1))sum[j1]-=(1LL<<j1);
				else sum[j1]+=(1LL<<j1);
		}
	for(j=0;j<i;j++){
		I[j].x=sum[j];
		I[j].id=j;
	}
	sort(I,I+j,rule);
	for(j=0;j<min(k,i);j++)
		if(I[j].x>0)
			ans+=(1<<I[j].id);
	ans+=(1<<i);
	cout<<ans;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值