「题解」洛谷 P9089 「SvR-2」Work
do_while_true · · 题解
来个时间复杂度真的线性的做法。
前面的部分和题解区那个 AC 自动机做法相同。先都 reverse 一下,找下充分条件,一个串
然后考虑根据这个必要性的构造划分等价类来 dp。
首先
然后
注意到这里并不需要求出 AC 自动机的转移边,仅需要 Trie 边以及 fail 树。所以这样构建:
考虑
x 是由 Trie 中的父亲fa 通过c 转移边得到的。那么检索y=fail_{fa} ,若y 存在字符c 的转移边,则令x 指向tr_{y,c} ;否则,令y\gets fail_y 即不断跳 fail 检查是否有c 出边。若到了初始节点(Trie 的根节点)依然没有c 出边,则令fail_x 指向初始节点。
对于每个串在 Trie 中自顶向下考虑尝试寻找 fail 的
所以总的时间复杂度是
#include<bits/stdc++.h>
#define fi first
#define se second
#define mp make_pair
using namespace std;
typedef pair<int,int> pii;
const int N=1000010;
int n;
int tr[N][26],fa[N],fail[N],dep[N],g[N],tot;
int head[N],ent;
struct Edge{
int nxt,to,col;
}e[N];
int f[N];
void add(int x,int y,int c){
tr[x][c]=y;fa[y]=x;dep[y]=dep[x]+1;
e[++ent]={head[x],y,c};head[x]=ent;
}
void Ins(string &s){
int u=0,l=s.size();
for(int i=0;i<l;i++){
if(!tr[u][s[i]-'a'])add(u,++tot,s[i]-'a');
u=tr[u][s[i]-'a'];
}
}
void build(){
queue<pii>q;
for(int i=0;i<26;i++)if(tr[0][i])q.push(mp(tr[0][i],i));
while(!q.empty()){
int u=q.front().fi,c=q.front().se;q.pop();
int x=fail[fa[u]];
while(x&&!tr[x][c])x=fail[x];
if(x!=fa[u])fail[u]=tr[x][c];
g[u]=fail[u]?g[fail[u]]:dep[u];
for(int i=head[u];i;i=e[i].nxt)q.push(mp(e[i].to,e[i].col));
}
}
string s[N];
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
cin >> n;
for(int i=1;i<=n;i++){
cin >> s[i];
reverse(s[i].begin(),s[i].end());
Ins(s[i]);
}
build();
long long ans=0;
for(int o=1;o<=n;o++){
int m=s[o].size(),u=0;f[0]=0;
for(int i=0;i<m;i++){
u=tr[u][s[o][i]-'a'];
f[i]=f[i-g[u]]+1;
ans+=f[i];
}
}
cout << ans << '\n';
return 0;
}