UOJ219 【NOI2016】优秀的拆分

UOJ219 【NOI2016】优秀的拆分

\(\operatorname{Runs}\)

这道题普通做法应该是\(\operatorname{SA/SAM}\),但是使用了\(\operatorname{Runs}\)就相当简单了。

题目中给出的\(AA,BB\)形式就是\(\operatorname{Runs}\)最容易找的平方串,所以我们可以先求出这个串的\(\operatorname{Runs}\)

\(e_i\)为以\(i\)结尾的平方串个数,\(b_i\)表示以\(i\)开头的平方串个数,显然答案就是:

\[\sum e_i b_{i+1} \]

注意到平方串的总数可以达到\(O(n^2)\)级别,直接枚举未免太过暴力,我们选择枚举平方串的一半长度,那么在一个\(\operatorname{Run}\)中,我们枚举的长度显然就是周期的倍数,长度确定以后,平方串开头和结尾就是一段连续的区间,可以直接差分打标记,最后一遍前缀和处理。

枚举周期的倍数的复杂度,相当于\(\operatorname{Runs}\)的指数和,因此这一部分复杂度为\(O(n)\)

加上求\(\operatorname{Runs}\)的复杂度,总时间复杂度为\(O(Tn \log n)\)(当然能够\(O(n)\)\(\operatorname{Runs}\)即可将复杂度将为\(O(Tn)\))。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 30005
#define ll long long
using namespace std;
const int p=999999883;
const int bs=277;
int T,n,m;
int m0[N],h[N];
int t,st[N],Ly[N];
int s1[N],s2[N];
char s[N];
ll ans;
struct Run
{
    int l,r,p;
    Run () {}
    Run (int A,int B,int C):l(A),r(B),p(C) {}
    bool operator < (const Run &A) const
    {
        if (l!=A.l)
            return l<A.l;
        return r<A.r;
    }
}runs[N << 1];
bool ckl(int l,int r,int T)
{
    return ((ll)h[l]-(ll)h[l-T]*m0[T]-h[r]+(ll)h[r-T]*m0[T])%p==0;
}
bool ckr(int l,int r,int T)
{
    return ((ll)h[l+T-1]-(ll)h[l-1]*m0[T]-h[r+T-1]+(ll)h[r-1]*m0[T])%p==0;
}
int extl(int l,int r)
{
    int L(0),R(l),g(0);
    while (L<=R)
    {
        int mid(L+R >> 1);
        if (ckl(l,r,mid))
            g=mid,L=mid+1; else
            R=mid-1;
    }
    return g;
}
int extr(int l,int r)
{
    int L(0),R(n-r+1),g(0);
    while (L<=R)
    {
        int mid(L+R >> 1);
        if (ckr(l,r,mid))
            g=mid,L=mid+1; else
            R=mid-1;
    }
    return g;
}
bool cmp(int l,int r)
{
    int len(extr(l,r));
    return s[l+len]<s[r+len];
}
void Lyndon(bool op)
{
    Ly[n]=n,t=0,st[0]=n+1,st[++t]=n;
    for (int i=n-1;i;--i)
    {
        while (t && cmp(i,st[t])==op)
            --t;
        Ly[i]=st[t]-1,st[++t]=i;
    }
}
void check(int l,int r)
{
    int cl(extl(l,r)),cr(extr(l,r));
    if (cr+cl-1>=r-l)
        runs[++m]=Run(l-cl+1,r+cr-1,r-l);
}
int main()
{
    m0[0]=1;
    for (int i=1;i<=30000;++i)
        m0[i]=(ll)m0[i-1]*bs%p;
    scanf("%d",&T);
    while (T--)
    {
        memset(s1,0,sizeof(s1)),memset(s2,0,sizeof(s2));
        ans=0,m=0,scanf("%s",s+1),n=strlen(s+1);
        s[n+1]=0;
        for (int i=1;i<=n;++i)
            h[i]=((ll)h[i-1]*bs+s[i])%p;
        for (int op=0;op<=1;++op)
        {
            Lyndon(op);
            for (int i=1;i<=n;++i)
                check(i,Ly[i]+1);
        }
        sort(runs+1,runs+m+1);
        for (int i=1;i<=m;++i)
            if (runs[i].l!=runs[i-1].l || runs[i].r!=runs[i-1].r)
            {
                int l(runs[i].l),r(runs[i].r),p(runs[i].p);
                for (int len=p;(len << 1)<=r-l+1;len+=p)
                {
                    ++s1[l],--s1[r+2-(len << 1)];
                    ++s2[l+(len << 1)-1],--s2[r+1];
                }
            }
        for (int i=1;i<=n;++i)
            s1[i]+=s1[i-1],s2[i]+=s2[i-1];
        for (int i=1;i<n;++i)
            ans+=(ll)s2[i]*s1[i+1];
        printf("%lld\n",ans);
    }
    return 0;
}
posted @ 2021-04-23 19:26  GK0328  阅读(91)  评论(0编辑  收藏  举报