牛客练习赛45----D-Data Structure

首先发出题目链接:
链接:https://ac.nowcoder.com/acm/contest/847/D
来源:牛客网
涉及:按位运算


题目如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
说实话,按位运算挺重要的,快速幂有了按位运算才降低了复杂度,有和无本身就是对立的,有了0和1所代表的按位运算,才使得我们能够从一个数的结构上分析或者改变数字


我们先来说如何初步地得到一个最大的k–or–and的值

要让k-or-and值最大,首先是先让分段后每一段的or值最大,那么and后的值当然就是最大的,要想找到这个每一段中or的最大值,可以通过枚举的方式,由于这个最大or数的二进制位最大只有30位,可以从二进制位的最高位开始枚举每一段or的最大值。

举个例子
我们假设第30位是1,就得到100…000(共29个0),然后贪心的验证,存不存在k段,使得每一段的or值的二进制位第30位为1,如果存在,那么最大or值二进制位的第30位一定是1,否则就是0.

然后,假如第30位是1,再来假设第29位也是1,就得到1100…000,在来贪心的验证能不能将序列分成k段,使得每一段的or值的二进制位第30位为1且二进制第29位为1,如果存在,那么最大or值二进制位的第29位也是1,否则就是0.

再然后,假设第29位不能为1,那么再假设第28位是1,就得到10100…0000,在来贪心的验证能不能将序列分成k段,使得每一段的or值的二进制位第30位为1,二进制第29位为0,二进制第28位为1(以此类推)…

问题:如何来贪心得进行验证?
加入当前得到的数是101000…000(ans),从序列第一个数开始往后一直或,直到或到一个数,使得这一系列的数或之后的值(cnt)与ans的与值等于ans(表示ans的二进制位为1,cnt在这个位置也是1,即**(cnt&ans)==ans**),于是刚刚或的一系列的数即为这个序列的一段,然后这样再继续找下一段,满足了每一段的or值都是ans,然后看一看是否分成有k段,如果有,表示验证成功,否则验证失败。

按照以上的方法,判断完30位的每一位,就得到了一个数,这个数就是最大的k-or-and值

bool check(ll ans){
	ll cnt=0,ant=0;//cnt是当前一系列的或值,ant代表当前已分段数
	for(int i=1;i<=n;i++){
		cnt|=num[i];
		if((cnt&ans)==ans)	ant++,cnt=0;//满足条件,分段数加一,cnt清零,从新开始新一段的或
	}
	//再判断是否已经分了至少k段
	if(ant>=k)	return true;
	else	return false;
}

下面来说一说如何修改

不多说,直接把序列中每一个值进行修改,再套用上面求k-or-and最大值的方法肯定超市。

所以得找技巧!

根据或和与的确定性可知:
1.当序列所有的数或上一个x,当x二进制第i位是1,那么序列中所有数二进制的第i位都会变成1,最后k-or-and值得二进制位的第i位就锁定为1;
2.当序列所有的数与上一个x,当x二进制第i位是0,那么序列中所有数二进制的第i位都会变成0,最后k-or-and值得二进制位的第i位就锁定为0;

但是,不一定最后k-or-and值二进制每一位的数都发生变化,也就是说,只有那些被锁定的值是确定的,没有被锁定的值仍然是未知的

ps:每一次或上或者与上一个数,可能出现新的位置被锁定了,也有可能没有出现新的被锁定的位置。


于是,求最大k-or-and的方法就会发生变化

不需要再一个个判断所有的30和位置,我们只用讨论那些没有被锁定的位置

最后的k-or-and的最大值就是没有锁定位置的值加上锁定位置的值

但是注意,在这里需要剪枝
如果上一次或上或者与上一个值,没有出现新的锁定的位置,那么,我们可以跳过判断没有锁定位置值得这一步骤!可以用一个数组来判断每一个位置是否被锁定,然后用一个bool类型标记,来判断是否出现新的锁定位置


举个例子:

比如题目例子(这里只讨论求k-or-and值,由于一开始没有任何位置被锁定,所以要全部位都要讨论
n=3,k=2,序列为{11,30,4}

序列二进制数
111011
3011110
4100

可以从10000开始讨论:
1.当ans=10000时

序列二进制数满足条件分段后的or值
111011
301111011,30为一段,or值为11111
4100

只能分成一段{11,30,4},不满足条件,故ans第五位为0

2.当ans=1000时

序列二进制数满足条件分段后的or值
11101111为一段,or值为1011
301111030为一段,or值为11110
4100

可以分成两段{11}{30,4},满足条件,故ans第四位为1

3.当ans=1100时

序列二进制数满足条件分段后的or值
111011
301111011,30为一段,or值为11111
4100

只能分成一段{11,30,4},不满足条件,故ans第三位为0

4.当ans=1010时

序列二进制数满足条件分段后的or值
11101111为一段,or值为1011
301111030为一段,or值为11110
4100

可以分成两段{11}{30,4},满足条件,故ans第二位为1

5.当ans=1011时

序列二进制数满足条件分段后的or值
11101111为一段,or值为1011
3011110
4100

只能分成一段{11,30,4},不满足条件,故ans第一位为0

所以最大的k-or-and值为1010(10)。


代码如下:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#define INF 200005
using namespace std;
typedef long long ll;
int n,k,q;//题目定义的变量
int flag;//flag存题目定义的操作类型数
ll num[INF],x;//num数组存题目中的A数组
int bit[32];//bit数组存被锁定的位置,以及锁定位置对应的锁定值,当bit[I]=-1表示这一位未被锁定
bool updata=true;//updata判断于或者或之后是否出现新的锁定位
bool check(ll ans){
	ll cnt=0,ant=0;//ant存当前已经分段数,ant存当前一段的or值
	for(int i=1;i<=n;i++){//贪心的判断能不能分成k段
		cnt|=num[i];
		if((cnt&ans)==ans)	ant++,cnt=0;//能,则重新开始分段
	}
	if(ant>=k)	return true;//能贪心分成k段
	else	return false;
}
int main(){
	cin>>n>>k;
	ll ans;//在查询最大K-OR-AND事使用,用来贪心枚举未锁定位置的的最大分段or值
	memset(bit,-1,sizeof(bit));//把bit数组初始化为-1,表示所有位置都没有被锁定
	int i;
	for(i=1;i<=n;i++)	cin>>num[i];
	cin>>q;
	while(q--){
		cin>>flag;
		if(flag==3){
			ll final=0;//final存所有被锁定位置中,锁定值为1的贡献
			for(i=0;i<=30;i++)
				if(bit[i]==1)	final+=(1<<i);
			if(updata){//如果没有出现新的所定值,就不用再次对未锁定的位置进行贪心
				ans=0;//初始化为0
				updata=false;
				for(i=30;i>=0;i--){
					if(bit[i]!=-1)	continue;//跳过锁定位置
					ans^=1<<i;//假设二进制这一位为1
					if(!check(ans))	ans^=1<<i;//如果不能为1,则为0
				}
			}
			cout<<final+ans<<endl;//锁定位置的值加未锁定位置的值就是答案
		}
		else if(flag==2){//与
			cin>>x;
			for(i=0;i<=30;i++)
				if(!(x&1<<i)){//判断x的二进制的第i位是不是为0
					if(bit[i]==-1)	updata=true;//如果是0,且k-or-and这一位未被锁定,则最大k-or-and这一位锁定
					bit[i]=0;//锁定为0
				}
		}
		else{//或
			cin>>x;
			for(i=0;i<=30;i++)
				if(x&1<<i){//判断x的二进制的第i位是不是为1
					if(bit[i]==-1)	updata=true;//如果是1,且k-or-and这一位未被锁定,则最大k-or-and这一位锁定
					bit[i]=1;//锁定为1
				}
		}
	}
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值