题型:字符串
题意:长度为n的字符串。求可重排回文子串的数目。
分析:
n范围10^5
最暴力的方法需要O(n^3),枚举起点终点,验证是否是回文串。复杂度多高,不可接受。
优化一点,由于子串可以重排,所以只需要根据子串长度的奇偶性和字母的奇偶统计即可验证回文串,这一维优化为O(1),总复杂度O(n^2),仍然不可接受。
所以就需要对O(n^2)下手了。
由于只有大小写52个字母,用二进制表示当前位置时各字母的出现次数的奇偶性。
例如串“abacb”
初始 000
a 100
b 110
a 010
c 011
b 001
这样的状态表示满足异或的性质,若当前状态在之前出现过,那么之前出现该状态位置到当前状态位置长度的字符串是可重排成为回文的。
对于奇数长度的字符串,可以让52位中的某一位反转,再看之前有木有出现过相同状态。
用long long表示状态,用map<LL,int>存每一个状态出现的次数。
如此以来,复杂度为O(n*52*log(n))。
但是还是会TLE,O(52*n)实在是优化不了了,只好采用hash把map的logn复杂度优化掉。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
#define mt(a,b) memset(a,b,sizeof(a))
using namespace std;
const int MOD = 1000007;
const int M = 300100;
int n;
char str[M];
/**hash*/
LL val[MOD+10];
int next[MOD*2];
int tote;
int cnt[MOD*2];
int first[MOD+10];
/**add a data*/
void add(LL a){
int sit = a % MOD;
for(int i = first[sit];~i;i=next[i]){
if(val[i] == a){
cnt[i] ++;
return;
}
}
val[tote] = a;
cnt[tote] ++;
next[tote] = first[sit];
first[sit] = tote ++;
}
/**query a's num*/
int siz(LL a){
int sit = a % MOD;
for(int i=first[sit];~i;i=next[i]){
if(val[i] == a){
return cnt[i];
}
}
return 0;
}
int main(){
while(~scanf("%d",&n)){
scanf("%s",str);
mt(cnt,0);
mt(first,-1);
tote = 0;
add(0);
LL ans = 0;
LL pre = 0;
for(int i=0;i<n;i++){
int id;
if(str[i]>='a' && str[i]<='z') id = str[i] - 'a';
else id = str[i] - 'A' + 26;
pre ^= (1LL<<id);
ans += siz(pre);///偶数长度子串
///奇数长度子串
for(int j=0;j<52;j++){
LL tmp = pre ^ (1LL<<j);
ans += siz(tmp);
}
add(pre);
}
printf("%lld\n",ans);
}
return 0;
}