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
*/
写的时候还是理解的不清晰。
其实写起来应该是要很顺畅的才对。
不够熟练?也许?
老师,我太想进步了😭