[日常训练]自动机
Description
确定优先状态自动机\((DFA)\)的节点被称为状态,边被称为转移,是一个有向图。每一个\(DFA\)的转移都被标记为一个字母。而且,对于每一个状态\(s\)和一个字母\(l\),最多只有一个转移从状态\(s\)开始,并且被标记为\(l\)。\(DFA\)有一个开始状态,和若干个结束状态。由\(DFA\)定义的语言是指,所有可通过按顺序记录从开始状态出发直到任一结束状态的路径上的字母所构成的单词组成的集合。
现在给你一个语言,请你输出其对应的状态数最小的\(DFA\)的状态数。若不存在,则输出\(-1\)。
Input
第一行包含一个整数\(n\),表示语言中的单词数。
接下来\(n\)行,每行包含一个单词。每个单词长度不超过\(30\),且只包含小写字母。所有单词保证不相同。
Output
输出一个整数,如题目要求。
Sample Input
3
fix
foo
ox
Sample Output
5
HINT
\(1\;\leq\;n\;\leq\;5000\)
Solution
显然,不存在无解的情况。
\(trie\)树是一个合法的但是比较劣的解。
合并后缀可以减少节点开支,用树哈希即可。
由于题目给的是图上所有从起点到终点的路径,所以要对结束节点打上标记,避免构图时节点数比标准答案少。
(人生第一道树哈希)
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define K 27
#define M 31
#define N 5001
#define T 150001
#define U 233329
#define V 666649
#define W 1000000007
using namespace std;
typedef long long ll;
struct trie{
int chd[K],key;
}t[T];
int l,n,cnt;char c[M];
ll f[K<<1];set<ll> s;
inline void insert(int u,int k){
int m=c[k]-'a'+1;
if(k==l){
if(t[u].key<=26)
t[u].key+=26;
return;
}
if(t[u].chd[m])
insert(t[u].chd[m],k+1);
else{
t[++cnt].key=m;
t[u].chd[m]=cnt;
insert(t[u].chd[m],k+1);
}
}
inline void print(int u){
printf("%d:%d\n",u,t[u].key);
for(int i=1;i<K;i++)
if(t[u].chd[i]){
printf("t[%d].chd[%d]=%d\n",u,i,t[u].chd[i]);
print(t[u].chd[i]);
}
}
inline ll hash(int u){
ll ret=0;
for(int i=1;i<K;i++)
if(t[u].chd[i])
ret=(ret^(hash(t[u].chd[i])*U%W))%W;
if(t[u].key>26) s.insert(ret^K);
else s.insert(ret);
ret=(ret+t[u].key*V%W*f[t[u].key]%W)%W;
return ret;
}
inline void init(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",&c);
l=strlen(c);
insert(0,0);
}
srand(time(0));
for(int i=(K<<1)-1;i;i--)
f[i]=rand()%U+1;
hash(0);
printf("%d\n",s.size());
}
int main(){
freopen("dfa.in","r",stdin);
freopen("dfa.out","w",stdout);
init();
fclose(stdin);
fclose(stdout);
return 0;
}