点击跳转
首先看到 ∑ ∣ S i ∣ ≤ 1 0 6 \sum |S_i| \le 10^6 ∑∣Si∣≤106,就考虑能不能分成长度大于根号、长度小于根号两种做
为了方便叙述,令 L = ∑ ∣ S i ∣ L=\sum |S_i| L=∑∣Si∣
长度超过 L \sqrt L L的串的个数不会多于 L \sqrt L L个
这部分串可以直接 k m p kmp kmp计算贡献
剩下的串,每个串长度都不超过 L \sqrt L L
建立AC自动机,然后把每个串在上面跑
跑到的那个点,沿着fail往上跳,沿途所有的深度平方之和加起来就是答案…吗
显然有点问题,一个串的不同前缀会被计入答案
AC自动机里面其实是个字典树,而字典树本质上来说是一个用来分类的数据结构
这就是说父亲对应的串的集合,肯定是其子树中的点对应的字符串集合的扩集
因此我可以在沿着fail往上跳的过程中,看看是不是其子树中已经有儿子算过了,如果算过了就要减去那个数量
这个操作可以借助dfs序
#include
#include
#include
#define iinf 0x3f3f3f3f
#define linf (1ll<<60)
#define eps 1e-8
#define maxn 1000010
#define maxe 1000010
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define drep(i,a,b) for(i=a;i>=b;i--)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
ll read(ll x=0)
{
ll c, f(1);
for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
for(;isdigit(c);c=getchar())x=x*10+c-0x30;
return f*x;
}
#define mod 998244353ll
struct Graph
{
int etot, head[maxn], to[maxe], next[maxe], w[maxe];
void clear(int N)
{
for(int i=1;i<=N;i++)head[i]=0;
etot=0;
}
void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
#define forp(_,__) for(auto p=__.head[_];p;p=__.next[p])
}G;
struct Easy_Tree
{
int depth[maxn], dist[maxn], tid[maxn], rtid[maxn], tim, size[maxn], rev[maxn];
void dfs(int pos, int pre, Graph& G)
{
tid[pos]=++tim;
rev[tid[pos]]=pos;
size[pos]=1;
forp(pos,G)if(G.to[p]!=pre)
{
depth[G.to[p]]=depth[pos]+1;
dist[G.to[p]]=dist[pos]+G.w[p];
dfs(G.to[p],pos,G);
size[pos]+=size[G.to[p]];
}
rtid[pos]=tim;
}
void run(Graph& G, int root)
{
tim=0;
depth[root]=1;
dfs(1,0,G);
}
}et;
struct ACautomaton
{
int trie[maxn][26], tot, fail[maxn], cnt[maxn], dep[maxn];
ll w[maxn];
void clear() //clear the arrays
{
for(int i=1;i<=tot;i++)cl(trie[i]), fail[i]=cnt[i]=0;
tot=1;
}
int insert(char *r, int len) //insert a string into trie tree
{
auto pos=1;
for(ll i=1;i<=len;i++)
{
pos = trie[pos][r[i]-'a'] ? trie[pos][r[i]-'a'] : trie[pos][r[i]-'a']=++tot;
cnt[pos]++;
dep[pos]=i;
}
return pos;
}
void build() //build the aca
{
queue<int> q;
int u, v, f;
q.push(1);
while(!q.empty())
{
u=q.front(); q.pop();
for(auto i=0;i<26;i++)
if(trie[u][i])
{
v=trie[u][i];
for(f=fail[u];f and !trie[f][i];f=fail[f]);
fail[v] = f ? trie[f][i] : 1;
//---计算w[v]
w[v] = ( w[fail[v]] + (ll)cnt[v]*dep[v]%mod*dep[v] )%mod;
ll p=fail[v], dfn=et.tid[v];
while(dfn < et.tid[p] or dfn>et.rtid[p])
p=fail[p];
(w[v] -= (ll)cnt[v]*dep[p]%mod*dep[p])%mod;
//---
q.push(v);
}
}
}
int move(int pos, int c)
{
c-='a';
for(;pos and !trie[pos][c];pos=fail[pos]);
return pos ? trie[pos][c] : 1;
}
}aca;
struct KMP
{
int n, next[maxn], t[maxn];
void build(char *r, int len)
{
int i, j=0;
n=len;
for(i=1;i<=len;i++)t[i]=r[i]; t[len+1]=0;
for(i=2;i<=len;i++)
{
for(;j and t[j+1]!=t[i];j=next[j]);
next[i] = t[j+1]==t[i]?++j:0;
}
}
int move(int pos, int x)
{
for(;pos and t[pos+1]!=x;pos=next[pos]);
return t[pos+1]==x ? pos+1 : 0;
}
}kmp;
char s[maxn];
int pos[maxn], len[maxn];
const ll S=1000;
int main()
{
int n=read(), i, j, k;
ll ans=0;
len[0]=1;
aca.clear();
rep(i,1,n)
{
pos[i] = pos[i-1] + len[i-1];
scanf("%s",s+pos[i]);
len[i]=strlen(s+pos[i]);
if(len[i]<S)
{
aca.insert(s+pos[i]-1,len[i]);
}
}
rep(i,1,aca.tot)rep(j,0,25)
if(aca.trie[i][j])G.adde(i,aca.trie[i][j]);
et.run(G,1);
aca.build();
rep(i,1,n)
{
int p=1;
rep(j,1,len[i])
{
p=aca.move(p,s[pos[i]+j-1]);
}
(ans+=aca.w[p])%=mod;
// de(p);
// de(aca.w[p]);
}
rep(i,1,n)
{
if(len[i]>=S)
{
kmp.build(s+pos[i]-1,len[i]);
rep(j,1,n)
{
int p = 0;
rep(k,1,len[j])p=kmp.move(p,s[pos[j]+k-1]);
(ans+=ll(p)*p)%mod;
}
}
}
printf("%lld",(ans+mod)%mod);
return 0;
}