2020牛客寒假算法基础集训营4 D 子段异或题解
原题链接:https://ac.nowcoder.com/acm/contest/3005/D
思路
两个核心公式 :
1【L,R】=【1,R】^【1,L-1】
2 A^A=0(充要)
题意要求求出异或值为0的子段数,我们可以通过记录每个值的前缀异或值来推出某个区间的异或值,如:【2,3】的异或值可以通过【1,3】^【1,1】来推出
,并且当且仅当【1,3】^【1,1】==0时,【2,3】==0。
可何时【1,3】^【1,1】才能等于0呢?
结合公式2,我们可以得知,当【1,3】= =【1,1】时,【2,3】==0。
因此我们可以将前缀异或值排序,方便我们寻找相同异或值的前缀区间。
再对异或值相同的区间个数N求组合C(2,N)即可(也就是N个里取两个的所有取法)
代码如下
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int N;
ll m[200001];
ll ans = 0;
int main()
{
cin >>N;
for (int i = 1; i <= N; i++)
{
scanf("%lld", m + i);
m[i] ^= m[i - 1];
}
sort(m + 1, m + 1 + N);
for (ll i = 1,j=i;i <= N; i=j+1)
{
j = i+1;
while (m[i] == m[j]&&j<=N)
j++;
j--;
ans +=(j-i+1)*(j-i)/ 2;
}
cout << ans<<endl;
return 0;
}
但这串代码是无法AC的,因为我们忘了考虑【L,R】中L=1的情况,此时我们要想要原来一样求出【L,R】,必须求得【1,0】,但这个区间显然是不存在的。
因此,我们在维护前缀异或值时直接判断其是否符合题意即可。
AC代码如下
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int N;
ll m[200001];
ll ans = 0;
int main()
{
cin >>N;
for (int i = 1; i <= N; i++)
{
scanf("%lld", m + i);
m[i] ^= m[i - 1];
if (m[i] == 0)//增加处
ans++;//增加处
}
sort(m + 1, m + 1 + N);
for (ll i = 1,j=i;i <= N; i=j+1)//i和j一定得开long long否则会溢出
{
j = i+1;
while (m[i] == m[j]&&j<=N)
j++;
j--;
ans +=(j-i+1)*(j-i)/ 2;
}
cout << ans<<endl;
return 0;
}