[BZOJ2084]Antisymmetry(二分+hash)

=== ===

这里放传送门

=== ===

题解

首先用【看数据范围猜复杂度大法】可以看出它的时间复杂度大概是O(nlogn) 的。。。。
然后它要统计的是符合条件的子串个数。一般统计子串的话都是枚举一个什么东西然后算它的贡献?比如枚举一个后缀,然后算这个后缀贡献了多少个合法的子串之类的?
这个题的话因为它有一个比较明显的类似回文的性质,所以可以考虑枚举回文中心然后计算每个点的贡献。如何在O(logn) 或者类似的时间里计算每个回文中心的贡献呢。。。
回文串有一个比较重要的性质就是它的单调性,就是说一个回文中心如果往两边延伸k个字符能构成一个回文串,那么往两边延伸1..k-1个字符也是可以构成回文串的。
可以发现这个题的“反对称”也是满足这个单调性的,那么就可以二分每一个回文中心的贡献了。
判定的时候使用hash,就是把正串S和取反后倒置的反串T都搞出来然后算出要求的那一段的hash值比较是否相等就可以了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ULL unsigned long long
using namespace std;
const ULL wer=2000001001;
int n;
long long ans;
char st[500010];
ULL hash[500010],rhash[500010],mul[500010];
bool same(int s1,int s2,int len){
    int t1,t2,dlt;
    ULL h1,h2;
    t1=s1+len-1;t2=s2+len-1;
    h1=hash[t1]-hash[s1-1];
    h2=rhash[t2]-rhash[s2-1];
    if (s1>s2){swap(s1,s2);swap(h1,h2);}
    h1*=mul[s2-s1];
    return h1==h2;
}
long long divide(int s1,int s2,int l,int r){
    int mid,ans=0;
    while (l<=r){
        mid=(l+r)>>1;
        if (same(s1,s2,mid)){
            ans=max(ans,mid);l=mid+1;
        }else r=mid-1;
    }
    return ans;
}
int main()
{
    scanf("%d\n",&n);
    gets(st);
    mul[0]=1;
    for (int i=1;i<=n;i++) mul[i]=mul[i-1]*wer;
    for (int i=1;i<=n;i++)
      hash[i]=hash[i-1]+st[i-1]*mul[i];
    for (int i=1;i<=n/2;i++) swap(st[i-1],st[n-i]);
    for (int i=1;i<=n;i++)
      if (st[i-1]=='0') st[i-1]='1';
      else st[i-1]='0';
    for (int i=1;i<=n;i++)
      rhash[i]=rhash[i-1]+st[i-1]*mul[i];
    for (int i=2;i<=n;i++){
        int j=n-i+1+1,len;
        len=min(n-i+1,n-j+1);
        ans+=divide(i,j,1,len);
    }
    printf("%I64d\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章