ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[可能有用科技]子序列自动机

2022-01-30 20:00:20  阅读:227  来源: 互联网

标签:子串 int st 有用 flag 序列 自动机


前言

我可能是有点大病才来学这种东西.

本来想去写计算几何和LCT,但是我太菜了学不会于是去写了一个广义 SAM 板子 甚至还 WA 了四次.

遂来学怪东西.

update from 学到一半:

我超fxj开卷多项式牛顿迭代了,好强!

子序列自动机

神 \(\mathrm{{\color{balck}w}{\color{red}{ind\_whisper}}}\) 说 : 所谓后缀自动机,就是通过后缀建立的自动机.

好像跑题了.

不要紧.

神 \(\mathrm{{\color{balck}w}{\color{red}{ind\_whisper}}}\) 说 : 所谓子序列自动机,就是通过子序列建立的自动机.

顾名思义,序列自动机可以且仅可以接受一个信号序列的子序列的自动机.

前缀后缀和子串都是子序列,无敌了!

这个自动机最大的问题是太 "博爱" 了,也就是能接受任原字符串任意的子序列且所有状态都是接受状态,导致无法解决有些只能考虑子串的情况.

首先我们再利用一下所学过最简单普适的自动机结构 : Tire.

把每一个子序列插入 trie 里.

然后这个想法已经超越了 \(naive\) , 简直是愚蠢.

然后再想一个想法,反正会出现大量的重复情况,不如放弃具体表述,每个状态只表示转移到那个位置.

然后就是一个 \(n + 1\) 个状态的自动机.

一个空状态,可以转移到以后每个状态.

然后 \(n\) 个状态,只能向更后面的任意状态转移.

且每个状态都是可接受的.

但是这样显然不行,因为无用的转移太多了.

考虑这样一个情况 : 字符串 aabbaa

初始状态 \(st_0\) 只有两种可行的转移种类 \(a,b\) 但是照上一种方式构造,却有六个可行转移选项,这显然是不优的.

那么贪心地想,反正可以向后面任意的转移,对于一类转移只需要求一个最考前的就完事了.

目前的建立时间是 \(\mathcal{O} (n^2)\),相比其他的自动机都是线性,显然这太拉了,想办法优化一下.

那么直接记录一下每种字符上一个出现的位置然后倒着建即可,复杂度降为 \(\mathcal{O} (n|\Sigma|)\).

但是可以发现这时候字符集很大地影响了复杂度,如果字符集太大(例如洛谷上子序列自动机的模板)甚至建立的时候会直接 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 : 查询带 \(\log\)

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.

首先看到题就知道这个玩意四倍常数,估计是 \(\mathcal{O} (n^2)\) 的做法.

1

\(a\) 的一个最短的子串,它不是 \(b\) 的子串.

那就对 \(b\) 建立后缀自动机然后枚举起点然后上后缀自动机上跑即可.

2

\(a\) 的一个最短的子串,它不是 \(b\) 的子序列.

对 \(b\) 建立子序列自动机,依然暴力 \(n^2\) 跑即可.

3

\(a\) 的一个最短的子序列,它不是 \(b\) 的子串.

考虑分别在 \(a\) 的子序列自动机上和 \(b\) 的 SAM上一起跳.

令 \(f_{i,j}\) 表示在 \(i\) 的序列自动机跳到状态 \(i\) , \(b\) 的 SAM 上跳到 \(j\) 时最多能添加几个字符且保证仍满足不是 \(b\) 子串.

然后枚举是否有一个可行的转移即可.

4

\(a\) 的一个最短的子序列,它不是 \(b\) 的子序列.

同理上一问,只是换成 \(a,b\) 都在子序列自动机上跑即可.

P4608 [FJOI2016]所有公共子序列问题

使用第一种.

需要高精.

需要压位高精,普通高精会MLE.

下辈子就补上.

标签:子串,int,st,有用,flag,序列,自动机
来源: https://www.cnblogs.com/AstatineAi/p/Subsequence-Automaton.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有