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;
}