给一串长度为n的字符串,求一共有多少个子串重排后可为回文串。n为3*10^5,字符集为大写字母和小写字母。
我尝试再现一下思路...
首先,子串一定是一个串的某个前缀的后缀(涨姿势),所以子串共有n(n-1)/2个。
其次,一个回文串一定满足:1.当回文串长为奇数时,该串中只有一种字符数量为奇数,其他字符数量全部为偶数。2.当回文串长度为偶数时,所有出现的字符数量都为偶数。存储一个串的奇偶性可以利用位存储的方式存储其状态mask,字符集大小为52,所以需要52位来,longlongint即可满足。如某子串状态为mask,该子串=前缀2-前缀1.则有mask = mask2^mask1.
如果直接暴力的话,那么复杂度是为o(n^2*52)。显然不行。
事实上,我们无需与每一个前缀均异或运算,因为很多子串都是不满足条件的,这里就可以用状态压缩去除很多废情况。首先,用哈希存储一下每一个前缀的mask出现过多少次,对于当前的状态,只有之前的前缀状态相同,或者异或后有一位为1(状压枚举),才满足条件,这样的话就可以利用哈希去查找合法情况。这里有一个细节,就是如果当前状态就为回文串,也需要加到答案里面。因为字符集为52,已经超过int表示范围,所以在位运算的时候要将1强制转换为long long int型,否则会出错。
考虑周全以后1y,感觉也是对今天wa了一下午加一晚上的一种安慰吧...
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
#define N 1000007
#define MAXN 300005
#define LL long long
int head[N],nn;
typedef struct{
LL num,key;
int next;
}node;
node ele[N];
char inp[MAXN];
LL save[MAXN];
int ret(char a){
if(a>='a'&&a<='z') return (a-'a');
else{
return (26+a-'A');
}
}
void Insert(LL tmp){
LL x;
x=tmp%N;
for(int i=head[x];~i;i=ele[i].next){
if(ele[i].key==tmp) {
ele[i].num++;
return ;
}
}
ele[nn].num=1;
ele[nn].key=tmp;
ele[nn].next=head[x];
head[x]=nn++;
}
LL Search(LL tmp){
LL x;
x=tmp%N;
for(int i=head[x];~i;i=ele[i].next){
if(ele[i].key==tmp){
return ele[i].num;
}
}
return 0;
}
int main()
{
int n;
while(~scanf("%d",&n)){
LL ans=0;
nn=0;
memset(head,-1,sizeof(head));
scanf("%s",inp);
LL tmp=0;
for(int i=0;i<n;i++){
tmp^=((LL)1<<ret(inp[i]));
Insert(tmp);
if(tmp==0) ans++;
ans+=Search(tmp)-1;
if(i%2==0){
int cnt=0;
for(int j=0;j<52;j++){
if(tmp&((LL)1<<j)) cnt++;
if(cnt>1) break;
}
if(cnt==1) ans++;
}
for(int j=0;j<52;j++){
ans+=Search(tmp^((LL)1<<j));
}
}
printf("%lld\n",ans);
}
return 0;
}