题目描述
长度为偶数的回文串被称为偶回文串。如果一个字符串重新排序之后能够成为一个偶回文串,则称为可回文的。
给一个字符串,求可回文的子串个数。字符串只含小写字母,单个字符串长度不超过10^5,所有数据字符串长度之和不超过2*10^6。
时间限制:3000ms 内存限制:131072kb
解题思路
由于可以重新排序,所以一个子串只要任一字母出现偶数次即可。于是将字母编号,h(c) = ±1 << (c - 'a'),第奇数次出现为正,第偶数次出现为负。于是问题转化为求某一子区间的编号和为0,维护前缀和Sum[i],又把问题转化为求相同的前缀和的点。这个时候就发现问题非常简单了,直接把Sum排序就好了。如果有x个点的Sum值相同,则对应x*(x-1)/2个合法区间,从前往后扫描完排序后的Sum数组即可得到答案。
有个小技巧:Sum排序时多加入一个0,就不用特判整个串是不是可回文的。
时间复杂度:O(n logn)
附:c++代码
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 using namespace std; 7 #define MaxLen 100020 8 9 typedef long long llt; 10 typedef unsigned long long ullt; 11 12 llt a[MaxLen], Sum[MaxLen]; 13 char s[MaxLen]; 14 bool vis[30]; 15 16 int main() 17 { 18 //freopen("L.in", "r", stdin); 19 llt i, Len, pos; 20 ullt tmp, ans; 21 while(scanf("%s", s) != EOF) 22 { 23 memset(vis, false, sizeof(vis)); 24 Len = strlen(s); 25 Sum[0] = 0; 26 for(i = 1; i <= Len; i++) 27 { 28 tmp = s[i - 1] - 'a'; 29 if(vis[tmp]) 30 { 31 a[i] = -(1 << tmp); 32 vis[tmp] = false; 33 } 34 else 35 { 36 a[i] = 1 << tmp; 37 vis[tmp] = true; 38 } 39 Sum[i] = Sum[i - 1] + a[i]; 40 } 41 sort(Sum, Sum + Len + 1); 42 ans = 0; 43 for(i = 0; i <= Len; i++) 44 { 45 if(i == 0) 46 { 47 pos = 0; 48 continue; 49 } 50 if(Sum[i] != Sum[pos]) 51 { 52 tmp = i - pos; 53 ans += tmp * (tmp - 1) / 2; 54 pos = i; 55 } 56 } 57 tmp = i - pos; 58 ans += tmp * (tmp - 1) / 2; 59 //ans += (i - pos) * (i - pos - 1) / 2; 60 //printf("%lld ", ans); 61 cout << ans << endl; 62 } 63 return 0; 64 }
另一种思路
这是官方给出的题解。
任意一个子串里某个字符的出现次数可以被表示成两个前缀字符串里出现次数的差,例如abbababbabbab的子串ababba,就可以表示成abbababba和abb的差,如果这两个前缀串里任意一个字符出现的次数在模2意义下是相等的,那么他们的差对应的子串就是一个合法的解。 以第i个字符结尾的前缀串和以第i+1个字符结尾的前缀串只差一个字符,可以通过线性递推得到所有的前缀串的26个字符出现次数的奇偶性,可以发现每个前缀串对应26个不是0就是1的数字,可以将其压缩成一个二进制数字si,si的第k位对应第k个字符出现次数的奇偶性,添加一个字符可以利用二进制不进位加法,其中二进制不进位加法可以用异或(xor)来表示。
题目链接:https://biancheng.love/contest-ng/index.html#/29/problems