Codeforces Round 936 (Div. 2) D. Birthday Gift

文章讲述了如何通过位运算策略解决编程问题,涉及到将数组分成连续子段,确保每个子段异或结果为0,以最大化满足特定条件下的子段数量k。作者给出了一个具体实例和代码实现来解释解题思路。
摘要由CSDN通过智能技术生成

cf刷题日记

样例输入:

8
3 1
1 2 3
2 2
1 1
2 2
1 3
2 3
0 0
3 2
0 0 1
4 2
1 3 3 7
2 2
2 3
5 0
0 1 2 2 1
 

样例输出:

2
2
1
2
3
-1
1
2
 

题目题意:将数组a分成任意个连续的子段,每一个子段长度大小至少为一且不能有剩余的元素,再将每一个子段异或的结果或起来,要使这个最终结果小于等于x,然后我们将子段的个数记为k,我们需要得到最大的k。

这是一个有关位运算的题目,写过这个题目(998. 起床困难综合症 - AcWing题库)的同学应该很容易想到对于这一类问题我们可以按二进制下的每一位来讨论。所以对于某一位或起来的结果如果想让它最小就是让它为0,即在这一位的每个子段异或起来的结果都为0,每个子段异或起来的结果想为0的话,就让每一个子段这一位的1的个数为偶数,所以整个a数组在这一位的1的个数也必须为偶数,如果为奇数的话,无论这个怎么划分数组a,都至少会让某一个子段的异或结果为1。

所以对于这个题,我们可以从高位开始迭代,为了方便起见,我们将x+1,让后求小于x+1的值。

对于如何划分这个数组a,我举一个很简单的例子,例如数组a的二进制表示为0100,1000,0000,1001,0000,0001,1110,1111,0000。这个四位二进制数组的最高位即第四位的值分别为0,1,0,1,0,0,1,1,0,我们想要让每一子段异或的结果都为0,就要让每个子段的1的个数为1,所以我们这样划分(0),(1,0,1),(0),(0),(1,1),(0),将这个数组a划分之后,相当于是把这个子段的数绑定起来,所以我们直接用这个子段异或后的结果来代替这个子段,这样给我们的下一次迭代带来的巨大的方便。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int n,x;

void solve()
{
	int k=-1;
	cin>>n>>x;
	x++;
	vector<int> a(n);
	for(int i=0;i<n;i++) cin>>a[i];
	
	for(int i=30;i>=0;i--)
	{
		vector<int> b;
		bool open=false;
		
		for(int j=0;j<a.size();j++)
		{
			if(open) b.back()^=a[j];
			else b.push_back(a[j]);
			if(a[j]&(1<<i)) open=!open;
		}
		
		if(x&(1<<i))
		{
			if(!open)
			{
				k=max(k,(int)b.size());
			}
		}
		else
		{
			if(open)
			{
				cout<<k<<endl;
				return;
			}
			a=b;
		}
		
	}
	
	cout<<k<<endl;
}

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--) solve();
	return 0;
}

好了,你已经基本了解了这个题的写法,接下来是对这个代码关键代码的逐句讲解分析:

#include <bits/stdc++.h>

using namespace std;

int n,x;

void solve()
{
	int k=-1;//k即为子段的个数,初始为-1,如果没有任何一个合适的划分方式就输出-1 
	cin>>n>>x;
	x++;//求小于x+1更加方便 
	vector<int> a(n);
	for(int i=0;i<n;i++) cin>>a[i];
	
	for(int i=30;i>=0;i--)
	//这个题的数据范围是2^30,我们从最高位第三十位开始迭代 
	{
		vector<int> b;//用b数组来储存将a划分的结果 ,b数组每一个数代表一个子段 
		
		bool open=false;
		//open表示这个子段是开放还是闭合的,如果是open为真就需要往这个子段加元素,
        //否则就是一个单独的子段 
		
		for(int j=0;j<a.size();j++)
		{
			if(open) b.back()^=a[j];
			//往这个子段继续加元素 
			
			else b.push_back(a[j]);
			
			if(a[j]&(1<<i)) open=!open;
			//如果出现了1,则代表这个子段还需要一个1,这样才可以让这个子段的异或结果为0 
		}
		
		if(x&(1<<i))
		{
			if(!open)
			{
				k=max(k,(int)b.size());
				//如果x在这一位为1,并且数组a可以划分为每一个都是偶数个1的子段,
                //那么这位可以为0,这个划分可以让最终结果小于x ,说明这个可能是k的结果 
			}
		}
		else
		//这个分支代表x在这一位为0,要让最终结果小于x,所以这一位必须为0 
		{
			if(open)
			//open为1,代表凑不出偶数个1,最后得到的结果肯定有1,
            //直接输出目前得到的k,然后return 
			{
				cout<<k<<endl;
				return;
			}
			
			a=b;
			//否则的话,保存这个划分,进行下一次迭代 
		}
		
	}
	
	cout<<k<<endl;//输出最终答案 
}

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);//优化输入输出 
	int T;
	cin>>T;
	while(T--) solve();
	return 0;
}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值