Codeforces Round 936 (Div. 2) D

AB没什么好说的
C二分答案,写的还是不够快,但是也很好了。

D的问题有点大。
我好像一直有一个不对的贪心再用,对于二进制的。
就是我会觉得一堆树或起来小于一个数字,这种限制是每个数字都小于那个数字就可以了。
但是实际上这就是一个很明显错误的贪心。
然后另一个反映就是,对于二进制比较的题面,要求小于某个数字,我就会觉得,直接找到最高位的那个1,让这个1变成0,剩下的就可以随便取了。
这并不是充要条件,很少的题目会用到这种东西。
所以这个想法绝大多数的时候都是在干扰我的判断的。
应该注意。

这题,我的想法就是我上面说的第一个问题导致的。

我写了一个trie树优化dp,\(f[i]\)表示到\(i\)点,保证合法时,能够分出的最大段数。
然后如果能够在前面找到一个位置,\(i\)的异或前缀和异或上这个位置的异或前缀和\(\leq x\),就让\(f[i]\)和这个位置的\(f\)值+1取max。
如果\(f[n]\)的值不存在,那就是-1。
很对啊。可惜,如果题目能把或运算改成取max,我这份代码就是标答。
太痛了。

这个高位到低位的贪心在和位运算有关的题目里面很常见。
但是我还是想不到,我觉得很巧妙,有些地方的处理,很可以学习。
我上面说的第二个错误其实就是一个判断的特殊情况,这个不应该被我当作可以解决整道题的方法,而应该只是对于一些子问题的使用方法。trie树里面就很多这个的应用。这题也有。
事实上,这题就是从高位到低位按位贪心,如果对于某个位置,目标数值的二进制表达=0,那就说明我们要异或上一些数字让这一位也是0,否则答案就不成立了。对于这一位,我们只有这一个选择。我们要做的就是让在这一位是1的数字每两个异或起来,因为我们只能一段一段的取,所以就有了一些位置我们不能分段。
如果这一位的x=1,那相当于我们这一维可以随便取了,假如我们这一维取了0,那我们后面不管怎么分段,只要保证了前面我们规定了不能分段的地方没有被断开,也就是那些高位异或起来是0,就可以随便断了,可以直接统计一次答案。
如果我们取了1,那后面就不一定是能随便断的,但是由于这里是1了,也就是这一位就是不会产生不能分段的限制了。直接开始下一位就好了,不需要记录到对后面的限制中去。但是如果要统计答案,这些限制依旧要算上。

这题,这个贪心的方式,我不是没有考虑过,但是我似乎是对按位的理解不够深刻,我总是觉得,如果有两个限制的范围相互交叉,就会直接导致错误。但是其实完全不会啊,如果交叉就都异或起来呗,如果这样导致了第三个这个位置的1进来,那根据我们异或取的规则,这个位置的第4个1一样会进来,如果没有第4个1,这个就不是这个部分需要管的内容了。

为什么我会这么觉得呢?
感觉就是对按位贪心的理解不够深刻。不同位之间的影响完全是不存在的。而且,很重要的是,我需要满足的限制只是他们需要在一起,只要分在了一组,他们的影响就是不存在,并没有什么如果还和什么数字在一起,这一位的影响就会再次出现。
这是一个。。保守的限制?
唉唉。我对这种保守的限制的感觉不够敏感和熟悉,导致我想不到,否决了这个想法。
按位贪心做少了。

应该就是这些问题了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char c=getchar();int a=0,b=1;
	for(;c<'0'||c>'9';c=getchar())if(c=='-')b=-1;
	for(;c>='0'&&c<='9';c=getchar())a=a*10+c-48;return a*b;
}
int n,x,a[100001],flag[100001];
int main()
{
//	freopen("1.in","r",stdin);
//	freopen("2.out","w",stdout);
	int T=read();
	while(T--)
	{
		n=read(),x=read();
		for(int i=1;i<=n;i++)
		{
			a[i]=read();flag[i]=0;
		}
		int ans=-1;bool fla=0;
		for(int i=30;i>=0;i--)
		{
			int cnt=0;
			int tmp=(x>>i)&1;int ok=0;
			if(tmp==1)
			{
				for(int j=1;j<=n;j++)
				{
					if(((a[j]>>i)&1)==1)ok^=1;
					if(ok==0&&flag[j]==0)cnt++;
				}
				if(ok==0)ans=max(ans,cnt);
			}
			else
			{
				for(int j=1;j<=n;j++)
				{
					if(((a[j]>>i)&1)==1)ok^=1;
					if(ok==1)flag[j]=1;
				}
				if(ok==1)
				{
					fla=1;
					cout<<ans<<endl;
					break;
				}
			}
		}
		if(fla==0)
		{
			int cnt=0;
			for(int i=1;i<=n;i++)if(flag[i]==0)cnt++;
			cout<<max(ans,cnt)<<endl;
		}
	}
	return 0;
}

/*
2
3 6
0 15 8
3 5
12 8 3


*/

写的时候还是理解的不清晰。
其实写起来应该是要很顺畅的才对。

不够熟练?也许?
老师,我太想进步了😭

posted @ 2024-03-23 15:35  HL_ZZP  阅读(17)  评论(0编辑  收藏  举报