题意:给定一个小写字母字符串,问符合下列条件的子串的个数:
1)子串中各个字母出现次数都是偶数。
2)至多存在一个?,?可以当做空,也可以当任意小写字母字符。
题解:
先不考虑?的情况,对于一个字符串,我们先定义一个26位的二进制表示字母情况,由于只用判断奇偶,所以我们用1表示这个位代表的字母个数为奇数,0为偶数。那么次增加一个字母,我们只要按位异或即可。之后我们定义dp[i]表示前i个字符组成字符串状态,并哈希hash[dp[i]]++,记录下之前出现dp[i]的个数,那么以i为尾字母的符合子串有hash[dp[i]个(若dp[j]==dp[i](j<i),那么求[j+1,i]段的状态就是dp[i]^dp[j],只有两者相等时其结果为0,即所有字母出现次数为偶数)。
我们可以用邻接表的形式哈希,详细见代码。然后在处理字母的时候,如果存在?,我们需要分成三部分,一部分是?之前的,一部分是?之后的,最后就是包含?横跨两段的;最后一部分,我们只要处理到?后,每次都枚举26个字母和空就可以了。
代码:
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <queue>
using namespace std;
const int maxn=2e4+10;
char a[maxn],b[maxn];
struct Map{
int head[maxn],next[maxn],key[maxn],val[maxn],tt;
Map(){tt=0;}
void clear()
{
memset(head,-1,sizeof(head));
tt=0;
}
int &get(int v)
{
int i,j,k,u=v%maxn;
for(i=head[u];i!=-1;i=next[i])
if(key[i]==v)return val[i];
key[tt]=v;
val[tt]=0;
next[tt]=head[u];
head[u]=tt;
return val[tt++];
}
int get2(int v)
{
int i,j,k,u=v%maxn;
for(i=head[u];i!=-1;i=next[i])
if(key[i]==v)return val[i];
return 0;
}
}mm;
int fun(int p,int n)
{
mm.clear();
int i,j,k=0,ans=0;
mm.get(0)++;
for(i=p;i<n;i++)
{
k^=(1<<(a[i]-'a'));
ans+=mm.get2(k);mm.get(k)++;
}
return ans;
}
int fun2(int p,int n)
{
mm.clear();
int i,j,k=0,ans=0;
mm.get(0)++;
for(i=0;i<p;i++)
{
k^=(1<<(a[i]-'a'));
mm.get(k)++;
}
ans+=mm.get2(k);
for(j=0;j<26;j++)
ans+=mm.get2(k^(1<<j));
for(i=p+1;i<n;i++)
{
k^=(1<<(a[i]-'a'));
ans+=mm.get2(k);
for(j=0;j<26;j++)
ans+=mm.get2(k^(1<<j));
}
return ans;
}
int main()
{
//freopen("D:\\in.txt","r",stdin);
int T;
scanf("%d",&T);
while(T--)
{
int i,j,k,n,ans=0,p=-1;
scanf("%s",a);
n=strlen(a);
for(i=0;i<n;i++)
if(a[i]=='?')p=i;
if(p==-1)ans=fun(0,n);
else
{
ans+=fun(0,p)+fun(p+1,n);
ans+=fun2(p,n);
}
printf("%d\n",ans);
}
return 0;
}