练气 方法记录


题解

这道题我们合理运用位运算来统计字符出现次数的奇偶性能达到事半功倍的效果。

异或(^)运算的性质(只考虑非负整数)

1.异或运算可以理解为两个数按位同\(0\)\(1\)。今有二进制数甲异或乙,若乙的第\(i\)位为\(1\),则将甲的第\(i\)位反转(即\(1\)变成\(0\)\(0\)变成\(1\));若乙的第\(i\)位为\(0\),则不对甲的第\(i\)位做任何处理。

2.一个数异或它本身,得到的结果为\(0\).

3.一个数异或另一个数偶数次,得到的结果还是那个数。

4.一个数异或另一个数后,再次异或那个数就会被还原回原数。

第3条性质可以用来统计每个字符出现次数奇偶性。

一共有26个小写字母,我们依次将它们转换成二进制上的每一位

然后我们用前缀和的思想处理整个字符串,并利用第4条性质还原某一部分,从而枚举完所有子串。

照着上面这张图讲,对于含有\(4\)个字符的字符串\(abca\),我们要是想获得后\(3\)\(bcd\),该怎么做呢?根据性质4,我们只需要用\(abcd\)异或\(a\)即可。这样以来就可以处理出所有的子串。处理好的子串是二进制串的样式。

接着,对于每个处理好的子串其中的每一位,若该位为\(1\),则对应字符出现了奇数次;若该位为\(0\),则对应字符出现了偶数次。

若原串为\(aabbcccd\),处理之后就会变成\(1100\),含义就是\(a\)出现偶数次,\(b\)出现偶数次,\(c\)出现奇数次,\(d\)出现奇数次。

那么,我们就只需要判断奇数次出现的字符数量是否超过\(1\)即可。

AC代码

#include<cstdlib>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1000010;
char s[N];
int a[N];
int sum[(1<<(26))-1];//sum储存了所有可能的子串,所以要开大 
ll ans=0;
int main()
{
	int n;
	scanf("%d",&n);
	getchar();
	for(int i=1;i<=n;i++)
	{
		s[i]=getchar();
		a[i]=(a[i-1]^(1<<(s[i]-'a')));//小写字母转为二进制上的位,a[]记录前缀异或值 
	}
	for(int i=1;i<=n;i++)
	{
		sum[a[i-1]]++;
		for(int j=1;j<=26;j++)
			ans+=sum[(a[i]^(1<<(j-1)))];
		ans+=sum[a[i]];
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2022-10-10 15:06  Fish4174  阅读(20)  评论(0编辑  收藏  举报