Description
对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。
现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。
Input
- 第一行一个正整数 N
(N≤5∗105) 。- 第二行一个长度为 N 的01字符串。
Output
- 一个正整数,表示反对称子串的个数。
Sample Input
8
11001011Sample Output
7
很显然,本题重点在于如何优化以前的 O(N2) 标准找回文串算法。
回忆以前的做法,我们是枚举中心点,再 O(N) 地向外延伸,增大半径。如果以“构成回文串”为要求,那么这个半径的增长是满足01单调性的,所以我们就可以采用二分答案找最大的满足回文串的半径。
那么找完半径后,如何快速判断左右两段是对称的?我们可以采用Hash来代替通过枚举判断回文的复杂度。于是此题 O(NlogN) 复杂度得解。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define B 200019
#define M 500005
#define P 1000000009
using namespace std;
int n,Base[M],Hnxt[M],Hback[M];
char str[M];
void init(){
scanf("%d %s",&n,str+1);
Base[0]=1;for(int i=1;i<=n;i++)Base[i]=1LL*Base[i-1]*B%P;
Hnxt[0]=0;for(int i=1;i<=n;i++)Hnxt[i]=(1LL*Hnxt[i-1]*B+str[i])%P;
Hback[n+1]=0;for(int i=n;i>=1;i--)Hback[i]=(1LL*Hback[i+1]*B+(str[i]^'0'^1^'0'))%P;
}
int Hashnxt(int L,int R){
int ans=Hnxt[R]-1LL*Hnxt[L-1]*Base[R-L+1]%P;
if(ans<0)ans+=P;
return ans;
}
int Hashback(int L,int R){
int ans=Hback[L]-1LL*Hback[R+1]*Base[R-L+1]%P;
if(ans<0)ans+=P;
return ans;
}
int main(){
init();
long long ans=0;
for(int pos=1;pos<n;pos++){//对称轴的位置在[pos,pos+1]内
int L=1,R=min(pos,n-pos),res=0;
while(L<=R){
int mid=L+R>>1;
if(Hashnxt(pos-mid+1,pos+mid)==Hashback(pos-mid+1,pos+mid))
res=mid,L=mid+1;
else R=mid-1;
}
ans+=res;
}
cout<<ans<<endl;
}
还有一种解法,就是利用Manacher算法在 O(N) 时间找到所有中心点的最大半径长度。这是非常经典的老算法了,它进一步利用回文串的性质:
- 在回文串对称点左侧出现的小回文串,必然在右侧也会出现。
那么每次出发时,如果已经有小回文串作为基础,就不需要从r=0再枚举一遍。
还有一个问题——为什么只是简化了枚举,复杂度却达到了
O(N)
?如果小回文串在大回文串当中,Mx不产生滑动;如果小回文串不被大回文串完全包含或者在其之外,Mx始终是线性向右滑动。总计复杂度就是Mx的位移复杂度
O(N)
。(我觉得还没讲清楚,当历史遗留问题好了)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 500005
using namespace std;
char str[M],buf[M<<1],tmp[M<<1];
int p[M<<1];
int main(){
int n;
scanf("%d",&n);
scanf("%s",str);
for(int i=0;i<=n;i++)buf[i<<1]=tmp[i<<1]='#';
for(int i=0;i<n;i++)buf[i<<1|1]=str[i],tmp[i<<1|1]=str[i]^'0'^1^'0';
n<<=1;
int id=0,mx=0;
long long ans=0;
for(int i=1;i<n;i++){
if(mx>i)p[i]=min(p[id*2-i],mx-i);
while(i-p[i]>=0&&buf[i-p[i]]==tmp[i+p[i]])++p[i];
if(i+p[i]>mx)mx=i+p[i],id=i;
if(buf[i]=='#')ans+=p[i]>>1;
}
cout<<ans<<endl;
}