望月Asta
2022-01-05 19:13:58
我可能是有点大病才来学这种东西.
本来想去写计算几何和LCT,但是我太菜了学不会于是去写了一个广义 SAM 板子 甚至还 WA
了四次.
遂来学怪东西.
update from 学到一半:
我超fxj开卷多项式牛顿迭代了,好强!
神
\mathrm{{\color{balck}w}{\color{red}ind\_whisper}} 说 : 所谓后缀自动机,就是通过后缀建立的自动机.
好像跑题了.
不要紧.
神
\mathrm{{\color{balck}w}{\color{red}ind\_whisper}} 说 : 所谓子序列自动机,就是通过子序列建立的自动机.
顾名思义,序列自动机可以且仅可以接受一个信号序列的子序列的自动机.
前缀后缀和子串都是子序列,无敌了!
这个自动机最大的问题是太 "博爱" 了,也就是能接受任原字符串任意的子序列且所有状态都是接受状态,导致无法解决有些只能考虑子串的情况.
首先我们再利用一下所学过最简单普适的自动机结构 : Tire.
把每一个子序列插入 trie 里.
然后这个想法已经超越了
然后再想一个想法,反正会出现大量的重复情况,不如放弃具体表述,每个状态只表示转移到那个位置.
然后就是一个
一个空状态,可以转移到以后每个状态.
然后
且每个状态都是可接受的.
但是这样显然不行,因为无用的转移太多了.
考虑这样一个情况 : 字符串 aabbaa
初始状态
那么贪心地想,反正可以向后面任意的转移,对于一类转移只需要求一个最考前的就完事了.
目前的建立时间是
那么直接记录一下每种字符上一个出现的位置然后倒着建即可,复杂度降为
但是可以发现这时候字符集很大地影响了复杂度,如果字符集太大(例如洛谷上子序列自动机的模板)甚至建立的时候会直接 TLE.
考虑直接对于每个字符存储一下其出现的位置,然后转移就靠二分查找即可.
Code 1 : 查询线性
constexpr int S = 26;// |Sigma|
struct SubsequenceAutomaton {
int len;
int ch[N][S];
inline void build(char *v,int n) {
static int nxt[S];len = n + 1;
rep(i,0,S - 1) nxt[i] = n + 1;
repb(i,n,0) {
memcpy(ch[i],nxt,sizeof(nxt));
nxt[v[i] - 'a'] = i;
}
}
inline bool query(char *s) {
bool flag = 1;int p = 0;
for(int i = 0;s[i];++i) {
if(ch[p][s[i] - 'a'] == len + 1) {
flag = 0;
break;
}
p = ch[p][s[i] - 'a'];
}
return flag;
}
}SQAM;
Code 2 : 查询带
constexpr int S = 100005;
struct SubSequenceAutomaton {
std::vector<int> st[S];
inline void build(int *v,int n) {
rep(i,1,n) st[v[i]].push_back(i);
}
inline bool query(int *t,int n) {
bool flag = 1;int p = 0;
rep(i,1,n) {
std::vector<int>::iterator pos =
std::upper_bound(st[t[i]].begin(),st[t[i]].end(),p);
if(pos == st[t[i]].end()) {
flag = 0;
break;
}
p = *pos;
}
return flag;
}
}SQAM;
P5826 【模板】子序列自动机
字符集比较大,需要使用第二种.
P4112 [HEOI2015]最短不公共子串
使用第一种.
大杂烩题,需要 SAM.
首先看到题就知道这个玩意四倍常数,估计是