题解 P1117 【[NOI2016]优秀的拆分 】

· · 题解

听说大佬们都是用SA做的    

然而SA的时间复杂度的确很优秀,缺点就是看不太懂……

  然后发现一位大佬用哈希华丽的过了此题,而且讲的特别清楚->这里

  我们只要考虑以每一个点结尾的AA串的个数u[i]和以每一个点开头的AA串的个数v[i],答案就是\sum _{i=1}^{n-1} u[i]*v[i+1]

  那么考虑如何求出uv

  我们考虑一下,枚举串A的长度len,然后每隔len个单位设置一个关键点。不难发现,每一个长度为len*2AA串,必定经过两个关键点

  然后考虑,只要求出相邻两个关键点往前的LCS和往后的LCP,如果LCS+LCP>=len,就表明存在长度为lenAA串。而且不难发现,所有经过这两个关键点的长度为lenAA串,肯定是连续的!所以我们可以找到这个区间,然后用前缀和差分,就可以避免区间修改了

  说了这么多,到底怎么求LCSLCP呢?(大佬:SA+ST表不是随便过的么)嗯,没错,二分。我们二分它们的长度,然后用哈希判断是否相等。这样虽然时间复杂度比起ST表多了个log,但起码更看得懂……

  时间复杂度是枚举len的调和级数,加上二分,为O(nlog^2n)

  ps:话说我也不明白调和级数是个什么玩意儿,只要知道枚举的复杂度是\sum _{i=1}^n \frac{n}{i} =O(nlogn)就行了……

  pps:管那么多干嘛能A不就行了么……话说这题明明纯哈希暴力就有95……某大佬讲课的时候还以这题为例嘲笑NOI近几年的出题水平(逃)

//minamoto
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
inline int read(){
    #define num ch-'0'
    char ch;bool flag=0;int res;
    while(!isdigit(ch=getc()))
    (ch=='-')&&(flag=true);
    for(res=num;isdigit(ch=getc());res=res*10+num);
    (flag)&&(res=-res);
    #undef num
    return res;
}
char sr[1<<21],z[20];int C=-1,Z;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
inline void print(ll x){
    if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=30005,mod=3e7+7;
char s[N];int n;
ll hash[N],mo[N],u[N],v[N],ans;
inline ll gethash(int l,int r){
    ll now=hash[l]-hash[r]*mo[r-l];
    now%=mod,now+=mod,now%=mod;
    return now;
}
int main(){
    int T=read();mo[0]=1;for(int i=1;i<=30000;++i) mo[i]=mo[i-1]*31%mod;
    while(T--){
        n=0;char ch;
        while((ch=getc())!='\n') s[++n]=ch;
        memset(u,0,sizeof(u)),memset(v,0,sizeof(v));
        hash[n+1]=0;
        for(int i=n;i;--i) (hash[i]=hash[i+1]*31+s[i]-'a'+1)%=mod;
        for(int L=1;L*2<=n;++L){
            for(int i=L<<1;i<=n;i+=L){
                if(s[i]!=s[i-L]) continue;
                int l=1,r=L,last=i-L,pos=0;
                //二分查找lcp和lcs 
                while(l<=r){
                    int mid=l+r>>1;
                    if(gethash(last-mid+1,last+1)==gethash(i-mid+1,i+1)) pos=mid,l=mid+1;
                    else r=mid-1;
                }
                int head=i-pos+1;
                l=1,r=L,pos=0;
                while(l<=r){
                    int mid=l+r>>1;
                    if(gethash(last,last+mid)==gethash(i,i+mid)) pos=mid,l=mid+1;
                    else r=mid-1;
                }
                int tail=i+pos-1;
                head=max(head+L-1,i);//防止越过两块 
                tail=min(tail,i+L-1);//防止跑到后面的块 
                if(head<=tail){
                    ++u[head-2*L+1],--u[tail+1-2*L+1];
                    ++v[head],--v[tail+1];
                    //为了差分
                    //因为head-2*L+1到tail-2*L+1开头的AA串增加的
                    //以他们的答案都可以++
                    //然后以head到tail结尾的AA串也++ 
                }
            }
        }
        ans=0;
        for(int i=1;i<=n;++i) u[i]+=u[i-1],v[i]+=v[i-1];
        for(int i=1;i<n;++i) ans+=v[i]*u[i+1];
        print(ans);
    }
    Ot();
    return 0;
}