UOJ429 【集训队作业2018】串串划分
UOJ429 【集训队作业2018】串串划分
\(\operatorname{Runs},\operatorname{dp}\),容斥
很容易想到利用\(dp\)计算答案,但是复杂度难以接受。
由于本题中存在多种限制,直接计算贡献不太方便,因此我们可以考虑容斥,我们需要想办法使得合法的划分贡献为\(1\),不合法的划分贡献为\(0\),考虑一下,一个循环串,必然是由一个最小循环节循环多次形成的,我们可以在根据循环节添上权值,定义\(C(S)\)表示\(S\)由最小循环节循环几次形成,定义\(V(S)=(-1)^{C(S)+1}\)。
我们现在来计算一下,把整个划分中,拥有相同循环节的小段作为一个大段来看,显然循环节出现超过\(1\)次必然不合法,而循环节出现\(1\)次是合法的。
如果循环节在大段中总共出现了\(k\)次,我们枚举划成几段来计算贡献,那么贡献为:
\[\sum_{i=0}^{k-1} {k-1 \choose i} (-1)^{k+1+i}
\]
显然,当\(k=1\)时,贡献为\(1\),当\(k>1\)时,贡献为\(0\),恰好除去了不合法的方案。
那么我们考虑如何计算,首先,循环串必然出现在\(\operatorname{Runs}\)中,对于我们当前\(dp\)到的位置\(i\),必然可以从包含该位置的\(\operatorname{Runs}\)转移而来,直接计算时间复杂度为\(O(n^2)\),无法接受,注意在同一个\(\operatorname{Run}\)中,加入以\(i\)结尾的循环串为\(SS \cdots SS\),我们可以分解为\(SS \cdots S,S\)两部分,那么对于第一部分,我们实际上已经\(dp\)过了,没必要进一步统计,直接在这个\(\operatorname{Run}\)上打标记处理一下即可。
最终\(dp\)的复杂度实际上与求本原平方串的时间复杂度相同,也就是\(O(n \log n)\)。
\(Code:\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#define N 200005
#define M 10000005
#define il inline
#define rint register int
#define pr pair<int,int>
#define mp make_pair
#define IT vector< pr > :: iterator
#define ll long long
using namespace std;
const int p=998244353;
const int bs=277;
int n,m,m0[N],h[N];
int t,st[N],Ly[N];
int cnt,fr[N];
vector< pr >e[N];
int f[N],g[N],u[M],v[M];
char s[N];
struct Run
{
int l,r,p;
Run () {}
Run (int A,int B,int C):l(A),r(B),p(C) {}
il bool operator < (const Run &A) const
{
if (l!=A.l)
return l<A.l;
return r<A.r;
}
il bool operator == (const Run &A) const
{
return l==A.l && r==A.r;
}
}runs[N << 1];
il 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;
}
il 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;
}
il 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;
}
il 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;
}
il bool cmp(int l,int r)
{
int len(extr(l,r));
return s[l+len]<s[r+len];
}
il void Lyndon(bool op)
{
t=0,Ly[n]=n,st[0]=n+1,st[++t]=n;
for (rint i=n-1;i;--i)
{
while (t && cmp(i,st[t])==op)
--t;
Ly[i]=st[t]-1,st[++t]=i;
}
}
il void check(int l,int r)
{
int cl(extl(l,r)),cr(extr(l,r));
if (cl+cr-1>=r-l)
runs[++m]=Run(l-cl+1,r+cr-1,r-l);
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
m0[0]=1;
for (rint i=1;i<=n;++i)
m0[i]=(ll)m0[i-1]*bs%p,h[i]=((ll)h[i-1]*bs+s[i])%p;
for (rint op=0;op<=1;++op)
{
Lyndon(op);
for (rint i=1;i<=n;++i)
check(i,Ly[i]+1);
}
sort(runs+1,runs+m+1);
m=unique(runs+1,runs+m+1)-runs-1;
for (rint i=1;i<=m;++i)
{
int l(runs[i].l),r(runs[i].r),per(runs[i].p),lim(min(l+per*3-2,r));
for (rint j=l+(per << 1)-1;j<=lim;++j)
fr[j]=++cnt,e[j].push_back(mp(fr[j],j-(per << 1)));
for (rint j=lim+1;j<=r;++j)
fr[j]=fr[j-per],e[j].push_back(mp(fr[j],j-(per << 1)));
}
f[0]=g[0]=1;
for (rint i=1;i<=n;++i)
{
f[i]=g[i-1];
for (IT it=e[i].begin();it!=e[i].end();++it)
{
u[it->first]=(u[it->first]+f[it->second])%p;
v[it->first]=(-v[it->first]-f[it->second])%p;
f[i]=((ll)f[i]-u[it->first]+v[it->first])%p;
}
g[i]=(g[i-1]+f[i])%p;
}
printf("%d\n",(f[n]%p+p)%p);
return 0;
}