[集训队作业2018] 串串划分
链接
毒瘤题。
首先这么多条件一看就很不好 dp,考虑合并条件 1,2,即我们钦定一个循环串是按照最小循环节分割开的。可以发现这样不符合条件1的串变成了不符合条件2的串。
然后考虑容斥,即“没有相同”看作 \(\sum_{k}(-1)^k\text{至少有k个相同的方案数}\)。
考虑一次转移恰好是原串的一次分割,无论这次分割的是否为循环串则有且仅有一种分割。用 \(R(s)\) 表示串 \(s\) 的最小循环节循环次数,即最小正周期,可以列出 \(f_i=\sum_{j}(-1)^{R(s[j+1:i])-1}\ f_j\)。
这个式子显然是 \(O(n^2)\) 的(R函数预处理)。
我们发现对于 \(R\) 是奇数的串这么算有点浪费,考虑换一种写法:
但是这样仍然是 \(O(n^2)\) 的。
为了解决这个问题,我们引入一个定义:本原平方串(primitive square)。
定义一个串 \(s\) 是本原平方串,当且仅当其最小正周期恰好为 \(\frac s 2\)。
可以证明,任何字符串的本原平方串的个数是 \(O(n\log n)\) 的。
考虑利用这个性质探究循环串 \(s[i:j]\) 的性质:如果 \(R(s[i:j])\text{ is even}\) 必然对应 \(s[i:j]\) 是由若干相同的本原平方串构成。
而一个本原平方串 \(s[i:j]\) 必定对应一个 Runs \((l,r,\frac{j-i}2)\)。所以我们不妨找到所有 Runs \((l,r,p)\),显然以任意起点 \(i\in[l,l+2p-1)\) 都有 \(s[i:i+2p]\) 是一个本原串。
对于起点 \(i\) 我们设置 \(i+2kp,k\in\mathbb{Z}\) 作为二元组 \((i,p)\) 的关键点,那么任意两个关键点之间都是合法的本原串。
所以对于 \(f_i\) 我们统计 \(i\) 位置的关键点信息。同时对于每个二元组我们统计当前所有的 \(f_p\) 之和。由于本原平方串的性质,这样做是 \(O(n\log n)\) 的。
总复杂度 \(O(n\log n)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<unordered_set>
#define ll long long
#define N 200010
#define mod 998244353
#define B 2333
using namespace std;
char str[N];
int s[N],n;
int h[N],bs[N];
int get(int l,int r){return (h[r]-1ll*h[l-1]*bs[r-l+1]%mod+mod)%mod;}
int lcp(int x,int y)
{
int l=1,r=n-max(x,y)+1,res=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(get(x,x+mid-1)==get(y,y+mid-1)) l=mid+1;
else r=mid-1;
}
return r;
}
int lcs(int x,int y)
{
int l=1,r=min(x,y),res=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(get(x-mid+1,x)==get(y-mid+1,y)) l=mid+1;
else r=mid-1;
}
return r;
}
bool cmp(int l1,int l2)//s[l1:]<s[l2:]
{
int l=lcp(l1,l2);
return s[l1+l]<s[l2+l];
}
int nxt[N];
int tt,pr[N*20],f[N],sf[N*20];
vector<int>g[N*20];
unordered_set<ll>fd;
void lyndon(bool ell)
{
for(int i=n-1;i;i--)
for(nxt[i]=i+1;nxt[i] && cmp(nxt[i],i)==ell;nxt[i]=nxt[nxt[i]]);
}
void make_runs()
{
for(int k=n-1;k;k--)
if(nxt[k])
{
int dl=lcs(k,nxt[k]),dr=lcp(k,nxt[k]),p=nxt[k]-k;
int l=k-dl+1,r=nxt[k]+dr-1;
if(dl+dr<=p || !fd.insert(1ll*r*N+l).second) continue;
for(int i=l-1;i<l+2*p-1 && i+2*p<=r;i++)
{
int x=++tt;pr[x]=p;
for(int j=i+2*p;j<=r;j+=2*p) g[j].push_back(x);
}
}
}
void init()
{
bs[0]=1;
for(int i=1;i<=n;i++) h[i]=(1ll*h[i-1]*B+s[i])%mod,bs[i]=1ll*bs[i-1]*B%mod;
}
int main()
{
scanf("%s",str+1);
n=strlen(str+1);
for(int i=1;i<=n;i++) s[i]=str[i]-'a'+1;
init();
lyndon(0);make_runs();
lyndon(1);make_runs();
int sm=f[0]=1;
for(int i=1;i<=n;i++)
{
f[i]=sm;
for(int v:g[i]) f[i]=(f[i]-2ll*(sf[v]=(sf[v]+f[i-2*pr[v]])%mod)%mod+mod)%mod;
sm=(sm+f[i])%mod;
}
printf("%d\n",f[n]);
return 0;
}