子序列自动机 学习笔记
因为在某场比赛上没学过子序列自动机而被搞了心态,听说是最简单的一种自动机,就赶紧来点亮技能树(
简介
子序列自动机,好像也称序列自动机,是一种可以快速判断字符串 是否是字符串 子串的算法。
这是一个很朴素的算法,本质上就是利用空间来换取时间。
实现
1. 原理
假设现有一字符串 ,我们定义一数组 ,其意义为对于串 ,从第 个位置后的字符串中元素 首次出现的位置。此时对于另一串 ,若 为 的子串,则 的每个位置的元素,可以利用 数组在 上的剩余字串得到匹配。
2. 构造 数组
对于构造 数组,我们可以从后往前枚举原串的每一个位置,先进行 的初始化,在根据原串当前位置元素对 数组进行修改。
代码如下:
for(int i=n-1;i>=0;i--)
{
for(int j=1;j<=m;j++) nxt[i][j]=nxt[i+1][j];
nxt[i][s[i+1]]=i+1;
}
时间复杂度为 ,其中 为元素个数。
3. 查找
构造出 数组后我们就可以进行查找子串操作。
我们定义指针 ,对于每次匹配,指针 都跳转到 的位置。如果当前 的指向值为空(或者为 ,取决于 的初始化),则说明母串中无法找到子串 。
代码如下:
int p=-1;
for(int i=0;i<n;i++)
{
p=nxt[p+1][t[i]];
if(!p) return false;
}
return true;
时间复杂度为 。
4. 其他优化
我们来看看这道模板 P5826 【模板】子序列自动机
瞄一眼数据范围, ,先不论时间够不够,空间是妥妥的会炸。
根据子序列自动机的思路,为了找第 位置后 元素的位置,我们可以考虑将原串中每一个 元素的位置记录下来,二分出第一个大于 的位置。
所以我们可以开 个 数组,存下下标,进行二分。
代码如下:
vector<int> nxt[MAXN];
int main()
{
type=Read();n=Read();q=Read();m=Read();
for(int i=1;i<=n;i++)
{
int x=Read();
nxt[x].push_back(i);
}
for(int i=1;i<=q;i++)
{
int len=Read(),now=0,flag=1;
for(int j=1;j<=len;j++)
{
int x=Read();
if(!flag) continue;
vector<int>::iterator it=lower_bound(nxt[x].begin(),nxt[x].end(),now+1);
if(it==nxt[x].end()) flag=0;
else now=*it;
}
if(flag) printf("Yes\n");
else printf("No\n");
}
return 0;
}
时间复杂度为 ,空间复杂度为
好像可以用主席树来写,但是我不会qwq
哪天会了来补
例题
- 判断是否原串子序列
子序列自动机的基础应用。
- 求一个字符串的子序列个数
处理出 数组之后可以对该串进行
设 表示从 到 中长度为 的子序列个数
则易得
queue<int> q;
int f[MAXN][MAXN],d[MAXN];
void Dp()
{
f[0][0]=1;
q.push(0);
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=1;i<=m;i++)
{
if(!nxt[u][i]) continue;
for(int j=0;j<=u;j++) f[nxt[u][i]][j+1]+=f[u][j];
d[nxt[u][i]]--;
if(!d[nxt[u][i]]) q.push(nxt[u][i]);
}
}
}
其实可以看出这个本质上就是在 上跑拓扑 ,所以 数组本质上就是一个
- 求两串的公共子序列个数
将两串的 数组先处理出来,设 表示 两串分别以位置 和位置 为起点的公共子序列个数,然后 一下就行了。
int f[MAXN][MAXN];
int Dfs(int i,int j)
{
if(f[i][j]) return f[i][j];
for(int now=1;now<=n;now++)
if(nxta[i][now]&&nxtb[j][now])
f[i][j]+=Dfs(nxta[i][now],nxtb[j][now]);
return ++f[i][j];
}
可以根据需要用 来储存个数。
- 求字符串的回文子序列个数
回文其实可以看作将原串分成两部分,由两端查找公共子序列。
所以可以正向和反向处理出 数组,注意反向处理的时候从右到左位置递增,然后进行 ,易得当匹配的两位置 时记录答案。由于这里我们只能统计长度为偶数的子序列个数,对于奇数长度的我们需要另加一。
int f[MAXN][MAXN];
int Dfs(int x,int y)
{
if(f[x][y]) return f[x][y];
for(int i=0;i<26;i++)
{
if(nxtl[x][i]+nxtr[y][i]>n+1) continue;
if(nxtl[x][i]+nxtr[y][i]<n+1) f[x][y]++;
f[x][y]+=Dfs(nxtl[x][i],nxtr[y][i]);
}
return ++f[x][y];
}
参考资料
题解 P1819 【公共子序列_NOI导刊2011提高(03)】
更新中(
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】