题目描述
译自 POI 2010 Stage 2. Day 0「Antisymmetry」
对于一个 0/1字符串,如果将这个字符串0 和 1 取反后,再将整个串反过来和原串一样,就称作「反对称」字符串。比如 00001111 和010101 就是反对称的,而 1001 就不是。
现在给出一个长度为 n 的 0/1 字符串,求它有多少个子串是反对称的,注意这里相同的子串出现在不同的位置会被重复计算。
输入格式
第一行一个正整数n 。n<=500000
第二行一个长度为 n的0/1 字符串。
输出格式
一行一个整数,表示原串的反对称子串个数。
样例输入
8
11001011
样例输出
7
如果s(i,j)是反对称则s(x,y),i<x<y<j也是反对称的(与回文串类似),所以具有单调性,枚举反对称串的中点,然后二分长度,求出最大长度len,则ans+=len/2;比较时可以用正反hash方便判断。
发现反对称串的长度是偶数,所以可以二分长度的一半,更容易实现。
#include<bits/stdc++.h>
#define N 500010
#define ll long long
#define base 2333
using namespace std;
int n;
char s[N];
int a[N],b[N];
const int md=1e9+7;
ll ha[N],hb[N],p[N];
ll geta(int l,int r){
return (ha[r]-ha[l-1]*p[r-l+1]%md+md)%md;
}
ll getb(int l,int r){
return (hb[l]-hb[r+1]*p[r-l+1]%md+md)%md;
}
int query(int x){
int l=1,r=n/2;
while(l<=r){
int mid=(l+r)>>1;
if(mid>=1&&x+mid<=n&&geta(x-mid+1,x+mid)==getb(x-mid+1,x+mid))l=mid+1;
else r=mid-1;
}
return r;
}
int main(){
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++){
a[i]=s[i]-'0';
}
for(int i=1;i<=n;i++)b[i]=1-a[i];
ha[0]=1;hb[n+1]=1;p[0]=1;
for(int i=1;i<=n;i++)ha[i]=(ha[i-1]*base%md+a[i])%md,p[i]=p[i-1]*base%md;
for(int i=n;i>=1;i--)hb[i]=(hb[i+1]*base%md+b[i])%md;
ll ans=0;
//for(int i=1;i<=n;i++)cout<<b[i]<<" ";cout<<endl;
for(int i=1;i<=n;i++){
ans+=query(i);
// cout<<query(i)<<endl;
}
printf("%lld",ans);
return 0;
}