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;
}
posted @ 2021-04-23 18:18  GK0328  阅读(167)  评论(0编辑  收藏  举报