本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
题目链接
题目
题目大意
给定一个长度为 的数组 ,有 个询问,每次询问对于给定的区间 ,能执行若干次下述操作:
- 选择两个正整数 ,且 是奇数,将 全部替换为他们的异或和,即 。
问是否使得 全部变成 0,如果不能就输出 -1,如果能就输出将 全部变成 0 需要的最小操作次数。每次询问相互独立。
思路
对于每一个询问:
- 显然 的话,不可能通过上述操作使得 全部变成 0,我们应该输出 -1。
- 如果被询问的整个区间已经全部都是 0 了,我们无需进行操作即可满足条件,所以应该输出 0。
- 又因为 ,如果 是奇数,我们可以直接选择区间 执行一次操作即可达成目的,输出 1。
- 现在我们的区间长度为偶数了,如果 或者 的话,我们可以无视掉为 0 的端点,对剩下长度为奇数的区间进行一次操作,输出 1。
- 容易发现,如果存在以 满足 且 是奇数,使得 ,显然从 到 这一段区间的长度也为奇数且异或和也为 0,我们可以通过 2 次操作达成目的,输出 2。
- 若不存在 满足条件 5,则无解,输出 -1。
其中条件 1 可以通过预处理前缀异或和解决,条件 2 可以通过维护前缀和来解决,条件 3 和 4 也可以直接判断,我们来说明条件 5 的判断方法。
首先我们可以预处理出以 为右端点的区间中的一个左段点 ,满足:
- 是奇数
- 是满足上述条件的最大值
这个东西只需要对奇偶下标分别开个 map 记录前缀异或和的值的对应的下标,然后每个位置在另一个 map 里找与之前缀异或和相等的位置再 +1 即可。显然这样做可以求出我们想要的东西。
在查询中我们只需要判断 和以 为右端点的最大的使得区间长度为奇数且区间异或和为 0 的左端点之间的大小关系即可。
代码
#include <bits/stdc++.h>
#define nnn printf("No\n")
#define yyy printf("Yes\n")
using namespace std;
using LL=long long;
const int N=500001;
int n,m;
map<int,int> mp0,mp1;
int xsum[N],a[N],lneq0[N],lnx0[N];
int getans(int l,int r)
{
if ((xsum[r]^xsum[l-1])!=0) return -1;
if (lneq0[r]<l) return 0;
if ((r-l+1)&1) return 1;
if (a[l]==0||a[r]==0) return 1;
if (lnx0[r]>l) return 2;
return -1;
}
int main()
{
scanf("%d%d",&n,&m);
int id=0;
for (int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
xsum[i]=xsum[i-1]^a[i];
if (a[i]) id=i;
lneq0[i]=id;
if (i%2)
{
mp1[xsum[i]]=i;
lnx0[i]=mp0[xsum[i]];
}
else
{
mp0[xsum[i]]=i;
lnx0[i]=mp1[xsum[i]];
}
}
int l,r;
for (int i=1;i<=m;++i)
{
scanf("%d%d",&l,&r);
printf("%d\n",getans(l,r));
}
return 0;
}