BZOJ2084 POI2010 Antisymmetry

来源:互联网 发布:网店美工是什么专业 编辑:程序博客网 时间:2024/05/29 18:38

POI2010 题解整理

Description

对于一个01字符串,如果将这个字符串0和1取反后,再将整个串反过来和原串一样,就称作“反对称”字符串。比如00001111和010101就是反对称的,1001就不是。

现在给出一个长度为N的01字符串,求它有多少个子串是反对称的。

Input

  • 第一行一个正整数N (N5105)
  • 第二行一个长度为N的01字符串。

Output

  • 一个正整数,表示反对称子串的个数。

Sample Input

8
11001011

Sample Output

7


Solution

很显然,本题重点在于如何优化以前的O(N2)标准找回文串算法。

回忆以前的做法,我们是枚举中心点,再O(N)地向外延伸,增大半径。如果以“构成回文串”为要求,那么这个半径的增长是满足01单调性的,所以我们就可以采用二分答案找最大的满足回文串的半径。

那么找完半径后,如何快速判断左右两段是对称的?我们可以采用Hash来代替通过枚举判断回文的复杂度。于是此题O(NlogN)复杂度得解。

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#define B 200019#define M 500005#define P 1000000009using 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 500005using 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;}
0 0