【日常小测】String

  • 题目来源:湖师大附中集训Day4T1
  • 本文为博主原创,未经许可不得转载

题目描述

给定一个字符串集合

求有多少个串使得这个串可以被断成两部分,每部分都是字符串集合中某个串的前缀

分析

首先把字符串集合建成AC自动机,那么如果不考虑重复的话,答案就是结点个数的平方,现在考虑去重问题

为了避免重复去重,我们定义一个串是重复的当且仅当它的某个后缀可以切掉一个前缀移动到前面的划分

这样我们就可以在AC自动机上跑,那么当前节点在fail树上的父亲所代表的串就是最长后缀

只需要统计切掉这个后缀后的前缀能接到几个串上就行了,随便维护一下就能做到

考虑一个问题:为什么要取最长后缀?

这是为了避免重复计算(画个图就知道了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include<bits/stdc++.h>
#define FILE "string"
#define MAXN 1000100
using namespace std;
typedef long long ll;
int Case,n,cnt,pos[MAXN],deep[MAXN],fail[MAXN],q[MAXN],size[MAXN],tr[MAXN][26];
ll ans;
char ch[MAXN];
void insert(){
int len=strlen(ch+1),now=0;
for(int i=1;i<=len;++i){
if(!tr[now][ch[i]-'a']) tr[now][ch[i]-'a']=++cnt,deep[cnt]=i;
now=tr[now][ch[i]-'a'];
}
}
void build(){
int head=0,tail=0;
for(int i=0;i<26;++i)if(tr[0][i])q[++tail]=tr[0][i];
while(++head<=tail){
int x=q[head];
for(int i=0;i<26;++i)if(tr[x][i]){
q[++tail]=tr[x][i];
int temp=fail[x];
while(temp&&!tr[temp][i])temp=fail[temp];
fail[tr[x][i]]=tr[temp][i];
}
}
for(int i=tail;i;--i) size[fail[q[i]]]+=(++size[q[i]]);
}
void dfs(int x,int depth){
pos[depth]=x;
if(fail[x]) ans+=size[pos[depth-deep[fail[x]]]]-1;
for(int i=0;i<26;++i)if(tr[x][i])dfs(tr[x][i],depth+1);
return;
}
int main(){
freopen(FILE".in","r",stdin);
freopen(FILE".out","w",stdout);
int __size__=256<<20;
char *__p__=(char*)malloc(__size__)+__size__;
__asm__("movl %0, %%esp\n"::"r"(__p__));
scanf("%d%d",&Case,&n);
for(int i=1;i<=n;++i){
scanf("%s",ch+1);
insert();
}
build(); dfs(0,0);
printf("%lld\n",(ll)cnt*cnt-ans);
return 0;
}
文章目录
  1. 1. 题目描述
  2. 2. 分析
,