【BCPC2015 偶回文串】状压 | 异或前缀和 | E

BCPC2015 偶回文串

时间限制: 3000 ms 内存限制: 131072 kb
总通过人数: 102 总提交人数: 135

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,L1] 含有的各个字母个数得到。所以这道题首先得求前缀和

  • 值得注意的是,我们并不需要精确知道某个串所包含每种字母的个数,而只需要知道每种字母的个数的奇偶性即可。所以可以进行状压,给每个字母分配一个 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×(k1) 这么多个可偶回文的串
  • 因此求出前缀和后进行排序,对每一段相等的前缀和进行计数和计算,累加即可得到答案(注意用 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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值