题目描述
定义字符串S为原串,SR为原串反转的到的字符串,SL为将原串中所有1变成0,0变成1得到的字符串。
一个非空字符串,对于他的每个第i个位置的字符和倒数第i个位置的字符都不同,那么这个串为
antisymmetry。特别的,如果一个字符串仅有0和1组成,那么当且仅当S=SLR的时候,他为
antisymmetry。
对于一个给定的01串(长度5*10^5),我们需要确定这个01串中连续.非空.且满足antisymmetric的片段的数目。
不同的片段,对应相同的子串的情形也需要计数。
分析
考虑对于每个符合条件的antis串,我们要统计其数目,就要考虑从他的哪个方向入手。首部?尾部?亦或是
中间位置,而考虑他反对称的性质,不难想到要枚举中间位置,那么只有logn的复杂度去处理答案了,又满
足当向两边拓展mid合法时,拓展mid-1的串一定是anstis串,这样用二分做就可以了,那二分中的判断怎
么做到O(1)呢,用hash来搞。
hash
以前没怎么做过hash处理字符串的题目,(这次发现hash好神奇,其实是因为此题的字符串终只有0或1两种情
况,所以hash可以处理到这么长(需要mod几个大质数,
注意
此题存入到字符数组中,是从0-n-1存储,而hash前缀和数组需要用到s[-1],所以前缀和数组从1开始存
储,即处理数据之前都把字符数组存储的位置为1-n
以后如果怕爆掉32位整数的话用1LL把它乘到前面来即可
code
#include<cstdio>
#include<iostream>
using namespace std;
const int maxx=500003;
const int P1=1000000007;
const int P2=9999971;
int f1[maxx],f2[maxx],a1[maxx],a2[maxx],to1[maxx],to2[maxx];
int n;
char s[maxx];
bool cal(int i,int mid)
{
long long x,y;
x=f1[i]-(1LL*f1[i-mid]*to1[mid])%P1+P1;
x%=P1;
y=a1[i+1]-(1LL*a1[i+mid+1]*to1[mid])%P1+P1;
y%=P1;
if(x!=y)
return(false);
x=f2[i]-(1LL*f2[i-mid]*to2[mid])%P2+P2;
x%=P2;
y=a2[i+1]-(1LL*a2[i+mid+1]*to2[mid])%P2+P2;
y%=P2;
if(x!=y)
return(false);
return(true);
}
int main()
{
// freopen("test.txt","r",stdin);
to1[0]=1; to2[0]=1;
scanf("%d%s",&n,s);
for(int i=1;i<=n;i++)
{
to1[i]=to1[i-1]<<1;
to1[i]%=P1;
to2[i]=to2[i-1]<<1;
to2[i]%=P2;
}
f1[0]=0; f2[0]=0;
for(int i=1;i<=n;i++)
{
f1[i]=f1[i-1]*2+s[i-1]-'0';
f1[i]%=P1;
f2[i]=f2[i-1]*2+s[i-1]-'0';
f2[i]%=P2;
}
a1[n+1]=0; a2[n+1]=0;
for(int i=n;i>0;i--)
{
a1[i]=a1[i+1]*2+'0'-s[i-1]+1;
a1[i]%=P1;
a2[i]=a2[i+1]*2+'0'-s[i-1]+1;
a2[i]%=P2;
}
long long sum=0;
for(int i=1;i<=n-1;i++)
{
int l=0,r=min(i,n-i);
int ans=-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(cal(i,mid))
{
ans=mid;
l=mid+1;
} else r=mid-1;
}
if(ans>0)
sum+=ans;
}
cout<<sum<<endl;
}