思路:
题目是多模匹配问题故容易想到AC自动机。
每次匹配都是以自己作为母串、考虑每次匹配成功对答案的贡献
设匹配到的节点是 i
不同的字符串可能有相同的前缀,故在字典树的每个节点中要记录以他为前缀的字符串个数 cnt[ ]
当前字符串的长度其实就是深度 deep[ ]
单点的贡献就是 cnt[i]*deep[i] 答案就是所有的累和。
思路很明确敲完自信测样例 很好比样例大很多!!!
思考发现题目要求的是最长前后缀,而这么写会存在一个字符串对答案贡献多次的问题。
多次贡献其实就是回跳,以样例为例
可以发现又跳了回来!
如何解决这个问题?
问题的核心是回跳问题,借助AC自动机的性质可以发现,假如当前在节点 i 不管怎么跳一定会跳到以 i 为结尾的字符串的最长相等前后缀的前缀节点
对单个点可以看作红线的跳法
故我们可以把 deep [ ] 数组修改成 当前节点到最长相等前后缀的前缀节点的距离就好了。
求最长相等前后缀在每次插入新串时 kmp 就好了。
Code:
#include <iostream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <algorithm>
#include <vector>
#include <string>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
//#include <unordered_map>
#define guo312 std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define ll long long
#define Inf LONG_LONG_MAX
#define inf INT_MAX
#define endl "\n"
#define PI 3.1415926535898
using namespace std;
const int N=1e6+10;
char a[200];
int to_[200];
void kmp(){
int len=strlen(a+1);
for(int i=2,j=0;i<=len;i++){
while(j&&a[i]!=a[j+1]) j=to_[j];
if(a[i]==a[j+1]) j++;
to_[i]=j;
}
}
int son[N][10],cnt[N],idx=0;
int w[N],_end[N];
void insert(int id){
int p=0,len=strlen(a+1);
for(int i=1;i<=len;i++){
if(!son[p][a[i]-'0']) son[p][a[i]-'0']=++idx;
p=son[p][a[i]-'0'],cnt[p]++;
w[p]=i-to_[i];
}
_end[id]=p;
}
int _next[N];
void build(){
queue<int> p;
for(int i=0;i<=9;i++){
if(son[0][i]){
p.push(son[0][i]);
}
}
while(!p.empty()){
int u=p.front(); p.pop();
for(int i=0;i<=9;i++){
if(!son[u][i]){
son[u][i]=son[_next[u]][i];
}
else{
_next[son[u][i]]=son[_next[u]][i];
p.push(son[u][i]);
}
}
}
}
int main(){
guo312;
int n; cin>>n;
for(int i=1;i<=n;i++){
cin>>a+1;
kmp();
insert(i);
}
build();
ll ans=0;
for(int i=1;i<=n;i++){
int now=_end[i];
while(now){
ans+=(ll)cnt[now]*(ll)w[now];
now=_next[now];
}
}
cout<<ans;
return 0;
}