题意:
现在有n个字符串,现在对于有一种操作:
对于当前的字符串,你可以删掉第一或者第二个字符
对于两个字符串S,T,称他们是可到达的当且仅当S经过若干次操作之后变成T
问你有多少对字符串是可到达的
题解:
很容易可以得到的一个点:
对于某一个串S,它可以到达的串只有对于位置i(0<=i<lenS)的后缀的前面加上一个i之前出现过的字符。就像这样:
红色的后缀前面粘上一个分割线之前出现过的字符(包括空)
那么我们先对于每个串从后往前建出字典树,然后对于每个串,从后往前再建一遍,第一次是建nex指针,第二遍是建num数组。
因为如果直接第一遍就建num数组的话,空间会开的很大,所以我们在第二遍的时候,对于存在的位置建num数组。
在建num数组的时候,对于当前串的当前后缀,加上所有这个串在之前出现过的字符,如果这个位置出现过,那么num++:
假设当前串是1356那么num数组就像这个样子的:
当然每个位置要存在才行。
最后查询的时候只需要查这个串出现的次数即可,当然要减掉它本身。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+5;
int num[N],nex[N][26],sum[N][26],cnt=1;
ll ans;
string s[N/5+5];
void add1(int x){
int n=s[x].length();
int now=1;
for(int i=n-1;~i;i--){
if(!nex[now][s[x][i]-'a'])
nex[now][s[x][i]-'a']=++cnt;
now=nex[now][s[x][i]-'a'];
}
}
void add2(int x){
int n=s[x].length();
int now=1;
for(int i=0;i<n;i++){
for(int j=0;j<26;j++){
if(s[x][i]-'a'==j)sum[i][j]=(i>0?sum[i-1][j]:0)+1;
else sum[i][j]=(i>0?sum[i-1][j]:0);
}
}
for(int i=n-1;~i;i--){
for(int j=0;j<26;j++)
if(sum[i][j]&&nex[now][j])
num[nex[now][j]]++;
now=nex[now][s[x][i]-'a'];
}
}
void finds(int x){
int n=s[x].length();
int now=1;
for(int i=n-1;~i;i--)
now=nex[now][s[x][i]-'a'];
ans+=num[now]-1;
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>s[i];
add1(i);
}
for(int i=1;i<=n;i++)
add2(i);
for(int i=1;i<=n;i++)
finds(i);
cout<<ans<<'\n';
return 0;
}