BCPC2015 偶回文串
Tags:状压 异或前缀和
题目描述
给定多个字符串,对于每个串,问其有多少个子串共含有偶数个字符,并且经过重新排列后能够成为回文串(称此串是重排后可偶回文的)。
输入
多组,以 E O F EOF EOF 结束。
每组一行一个只含小写字母的字符串,长度 ≤ 1 0 5 \leq 10^5 ≤105
所有字符串长度之和
≤
2
×
1
0
6
\leq 2 \times 10^6
≤2×106。
输出
对每组数据输出一个数,表示该字符串有多少子串是重排后可偶回文的。
输入样例
abba
abcba
输出样例
2
0
分析
-
这道题首先要想到的是某个串重排后可偶回文等价于其含有的每个字符都是偶数个(因为重排后再来个对称分布就一定是偶回文串了)。
-
然后还得运用前缀和的性质。也就是说某个索引区间 [ L , R ] [L, R] [L,R] 的这个子串含有的各个字符个数可以用 [ 0 , R ] [0, R] [0,R] 含有的各个字母个数减去 [ 0 , L − 1 ] [0, L-1] [0,L−1] 含有的各个字母个数得到。所以这道题首先得求前缀和。
-
值得注意的是,我们并不需要精确知道某个串所包含每种字母的个数,而只需要知道每种字母的个数的奇偶性即可。所以可以进行状压,给每个字母分配一个 b i t bit bit 位,如果该字母的个数是偶数则该位为 0 0 0,否则为 1 1 1。所以通过异或前缀和即可实现在 O ( 1 ) O(1) O(1) 内判断索引区间 [ L , R ] [L, R] [L,R] 这个串是否为重排后可偶回文的。
因此本题解法:
- 输入字符串后,预处理异或前缀和用来表示每一个前缀的字母奇偶个数信息(利用状压,一个 i n t int int 即可记录一个串所含 26 26 26 个字母个数的奇偶)
- 如果有 k k k 个前缀和相等,那么在 k k k 个中任选两个前缀和都可以得知他们中间那段串的异或和是0,也就是该串所含的字母都有偶数个,也就是说该串是重排后可偶回文的。所以 k k k 个相等的前缀和意味着它们之间存在 C k 2 = k × ( k − 1 ) 2 C_k^2 = \frac {k \times (k-1)}{2} Ck2=2k×(k−1) 这么多个可偶回文的串
- 因此求出前缀和后进行排序,对每一段相等的前缀和进行计数和计算,累加即可得到答案(注意用 l o n g l o n g long\ long long long)。
时间复杂度:预处理 Θ ( N ) \Theta (N) Θ(N) 的,排序 O ( N log N ) O(N\log N) O(NlogN) 的,遍历计数 Θ ( N ) \Theta(N) Θ(N) 的,总计 O ( N log N ) O(N\log N) O(NlogN) 。
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define PC putchar
template<typename T>void UPRT(const T a){if(a>=10)UPRT(a/10);PC(a%10+48);}
constexpr int ML(100005), MC(255);
int hs[MC];
char s[ML];
int sum[ML];
int main()
{
for (char c='a'; c<='z'; ++c)
hs[c] = 1 << (c-'a');
while (scanf("%s", s) != EOF)
{
int len = strlen(s);
sum[0] = 0;
for (int i=1; i<=len; ++i)
sum[i] = sum[i-1] ^ hs[s[i-1]];
int cnt = 1;
long long ans = 0;
std::sort(sum, sum+len+1);
for (int i=1; i<=len; ++i)
{
if (sum[i] == sum[i-1])
++cnt;
else
{
ans += (long long)cnt * (cnt-1) / 2;
cnt = 1;
}
}
ans += (long long)cnt * (cnt-1) / 2;
UPRT(ans), PC(10);
}
return 0;
}