大意
给你一个长度为N(N<=500000)的字符串S,字符集为0~9,从1开始编号。设F[i]为S[1~i]与S[i+1~N]两个串所能构成的本质不同的串。求 ∑N−1j=1F[j]∗100013N−j−1mod(109+7)。
分析
暴力O( N3) :直接枚举i,然后枚举左右两边每个子串,hash判断即可。
暴力O(N^2):往深处想想。本质不同的串,我们可以想想后缀自动机或者后缀数组能不能用上。我比较熟悉SA,提出了许多猜想,都不行,主要原因是从某个位置断开后,Height数组变化很大。
那就想想SAM吧,直接求不同的串会比较麻烦,考虑反过来:首先求出原串有多少个本质不同的串ans,然后对于每个位置i,我们只用统计有多少种本质不同的子串,每种的所有串都恰好经过了断开的地方。我们记为G[i],那么F[i]=ans-G[i]。
考虑一种串
Si
对G数组的贡献,我们记此串的长度为len,right集合最大位置为Max,最小为Min,那么:
1,当Max-len>=Min,总会有两个串不相交,那么无论i在哪里,这种串都会计入F[i],所以对G[i]无贡献;
2,当Max-len
代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=500005,mo=1000000007;
char ch;
ll f[N],g[N],prt,ans,tim[N];
int e[N*2][10],parent[N*2],mx[N*2],Max[N*2],Min[N*2],last[N],np,p,q,nq;
int b[N*4],next[N*4],first[N*2];
int i,j,n,l,r,x,ts,tt,mn;
void cr(int x,int y)
{
tt++;
b[tt]=y;
next[tt]=first[x];
first[x]=tt;
}
int insert(int last,int x)
{
np=++ts;
mx[np]=mx[last]+1;
Min[np]=Max[np]=i;
int w;
for(p=last;p>=0&&e[p][x]==0;p=parent[p])
e[p][x]=np;
if (p<0) parent[np]=0; else
{
q=e[p][x];
if (mx[q]==mx[p]+1) parent[np]=q;else
{
nq=++ts;
mx[nq]=mx[p]+1;
parent[nq]=parent[q];
Min[nq]=Min[q];
Max[nq]=Max[q];
parent[q]=parent[np]=nq;
fo(w,0,9) e[nq][w]=e[q][w];
for(;p>=0&&e[p][x]==q;p=parent[p]) e[p][x]=nq;
}
}
ans+=mx[np]-mx[parent[np]];
return np;
}
void dfs(int x,int y)
{
for(int p=first[x];p;p=next[p])
if (b[p]!=y)
{
dfs(b[p],x);
Min[x]=min(Min[x],Min[b[p]]);
Max[x]=max(Max[x],Max[b[p]]);
}
}
int main()
{
scanf("%d\n",&n);
parent[0]=-1;
fo(i,1,n)
{
scanf("%c",&ch);
last[i]=insert(last[i-1],ch-'0');
}
fo(i,1,ts)
{
cr(i,parent[i]);
cr(parent[i],i);
}
dfs(0,0);
tim[0]=1;
fo(i,1,n) tim[i]=tim[i-1]*100013%mo;
fo(i,1,ts)
{
x=i;
mn=mx[parent[x]]+1;
if (Max[x]-mx[x]>=Min[x]) continue;
l=Max[x]-mx[x]+1;
r=min(Max[x]-mn+1,Min[x]-1);
g[l]++;
g[r+1]--;
r++;
f[Min[x]]-=r-l;
}
fo(i,1,n) g[i]+=g[i-1];
fo(i,1,n) f[i]+=f[i-1]+g[i];
fo(i,1,n-1)
{
prt=(prt+(ans-f[i])%mo*tim[n-i-1]%mo)%mo;
}
printf("%lld",prt);
}
反思
1,SAM的部分中,循环的变量总是与外界冲突,导致错误,下次要尽量把所有变量都变为局部,虽然会一定程度上影响调试;
2,对SAM的性质掌握不够透彻:要计算所有的本质不同的串对答案的贡献,就应该每个状态都枚举一遍,我天真地只搞last[1~n]的状态,漏掉了不少串;
3,考虑SAM构造方法,某个点X的parent只有可能是编号小的点,所以我们不需要真的构建parent树然后跑dfs,只需要编号从大到小扫一遍就行了。