首页 > 其他分享 >【学习笔记】后缀自动机 SAM

【学习笔记】后缀自动机 SAM

时间:2024-01-17 19:05:11浏览次数:17  
标签:子串 SAM sam 后缀 text len fa endpos 自动机

一. 后缀自动机的定义

SAM(Suffix Automaton) 是一种有限状态自动机,仅可以接受一个字符串的所有后缀。

如果您不懂自动机,那么换句话说:

SAM 是一个有向无环图

称每个结点为状态,边为状态间的转移,每个转移标有一个字母,同一节点引出的转移不同。

SAM 存在一个源点 \(S\),称为初始状态,其它各结点均可从源点出发到达。也存在一个或多个终止状态 \(T\),每一条从 \(S\) 出发至 \(T\) 的路径所组成的字符串即为字符串的后缀。而对于这个字符串的每个后缀也必定在 SAM 的某条从 \(S\) 至 \(T\) 的路径上。

满足条件的同时,SAM 的点数是最少的。

接下来展示一个简单的 SAM:(以 abbb 为例)

红点为源点,蓝点为终止节点。箭头全部从左向右。

abbb 的所有后缀:

b: 1->5
bb: 1->5->6
bbb: 1->5->6->7
abbb: 1->2->3->4->7


二. \(\text{endpos}\) 数组

设 \(\text{endpos(t)}\),表示对于字符串 \(s\) 的任意一个非空字符串 \(t\),每一个 \(t\) 在 \(s\) 中出现的位置的右端。

abaabacaab,\(\text{endpos(ab)}=\{2,5,10\}\)。

显然会出现有两个不同子串 \(t_1,t_2\),而 \(\text{endpos}(t_1)=\text{endpos}(t_2)\),如 abaabacaab,\(\text{endpos(ab)=endpos(b)}=\{2,5,10\}\)。

那么我们称在 abaabacaab 里,ab,b 为同一个 \(\text{endpos}\) 等价类

对于 \(\text{endpos}\),有几个引理:

引理 \(1:\) 对于一个字符串中的两个子串 \(u,v\),若 \(\text{endpos(u)=endpos(v)}\),那么一个将是另一个的后缀

显然成立。因为若 \(v\) 不是 \(u\) 的后缀,那么必定有一个 \(v\) 的最后一位字符就必然不等于 \(u\) 的最后一位字符。

例子:

aabab

endpos(b)=endpos(ab)={3,5}
u=ab,v=b

引理 \(2:\) 对于一个字符串的子串 \(u,v(|u|\ge|v|)\),那么必然有 \(\text{endpos}(u)\subseteq \text{endpos}(v)\) 或 \(\text{endpos}(u)\cap\text{endpos}(v)=\varnothing\)

即要么其中一个的 \(\text{endpos}\) 包含另一个的,要么两者的 \(\text{endpos}\) 没有交集。

显然有可能 \(\text{endpos}\) 两者无交集。而若两者的 \(\text{endpos}\) 有交集,那么不妨设 \(x\) 被 \(\text{endpos}(v)\) 包含且不被 \(\text{endpos}(u)\) 包含,那么由于两者 \(\text{endpos}\) 有交集,那么 \(v\) 的必然为 \(u\) 后缀,那么显然 \(x\) 必然被 \(\text{endpos}(u)\) 包含。

例子:

原串:abacabbacabca
//example1:
u=aba,v=aca
endpos(u)={3}
endpos(v)={5,10}
//example2:
u=aca,v=ca
endpos(u)={5,10}
endpos(v)={5,10,13}

引理 \(3:\) 每个 \(\text{endpos}\) 等价类里的子串升序排序后长度连续,且前一个串是这个串的后缀。

也很显然。若字符串 \(u,v\) 在同一个 \(\text{endpos}\) 等价类里,且 \(u\) 的长度严格小于 \(v\) 的长度且 \(u,v\) 长度不连续。由引理 \(1\) 显然 \(u\) 是 \(v\) 的后缀,那么 \(u,v\) 中间的那部分子串减去某个前缀后加上 \(u\) 显然亦为同一个 \(\text{endpos}\) 等价类里。

例子:

原串:acabaacbacaba

u=aba
v=acaba
endpos(u)=endpos(v)={5,13}
w=(c)+aba=caba,endpos(w)={5,13}

三. \(\text{parent tree}\)

虽然 \(\text{endpos}\) 还有引理,不过涉及到 \(\text{parent tree}\),就必要先讲这个。

考虑对于一个字符串 \(s\),如果我们在 \(s\) 前面加上一个字符 \(c\),那么会将 \(\text{endpos}(s)\) 分成若干份(也可能是一份)

举个例子,还是 acabaacba,对于 ba,\(\text{endpos}(ba)=\{5,9\}\)。我们发现我们可以在 ba 前面加上 ac,那么就变为 abacba,而 \(\text{endpos}(aba)=\{5\},\text{endpos}(cba)=\{9\}\),正好将 \(\text{endpos}(ba)\) 分成两份。

由于引理 \(2\),那么两个非后缀关系的子串的 \(\text{endpos}\) 必无交集,所以考虑对于一个字符串的子串的 \(\text{endpos}\) 等价集节点,那么这些分割关系构成了树形结构

由于此时是个森林,考虑用一个超级根聚合起森林。

空串的等价集为全集(它无处不在),所以用空寂作为超级根。

那么,我们以 abcabstrstrs 为原串建一个 \(\text{parent tree}\):

fSP1Cn.png

引入一个概念:我们称等价类 \(u\) 中的最长子串 \(len_u\) 指的是:该等价类 \(u\) 的所有子串中长度最长的子串。

那么每个节点旁边蓝色的就是代表当前节点(等价类)的最长子串。

结点中的数字为 \(\text{endpos}\) 集。

这时,我们就引出新的 \(\text{endpos}\) 引理:

引理 \(4\):若两个等价类 \(p,q\),\(p\) 在 \(\text{parent tree}\) 上是 \(q\) 的父亲,那么 \(p\) 的最长子串的长度 \(+1\),等于 \(q\) 的最短子串的长度。

我们考虑在一个等价类中的某个子串前再添加一个字符,显然,若选择的子串是这个等价类中的最长子串,形成的字符串就归于其儿子的等价类中,否则就仍在这个等价类中。

如果选择的是最长子串,那这个新形成的字符串肯定这个儿子等价类 中最短的一个。

引理 \(5:\) 等价类的数量是 \(O(n)\) 级别的。

如果说在一张图上表示一个字符串的所有子串,那么显然可以建 trie。

但是 trie 的节点数是 \(O(n^2)\) 的。

考虑 \(\text{parent tree}\) 的节点数量。

对于一个原串中的等价类,其中的最长子串 \(s\),在 \(s\) 前添加一个字符且新字符串仍为原串子串,那么对于新的字符串 \(t\),必然会得到若干个新的等价类,这一过程相当于 在 \(\text{parent tree}\) 中将父亲分裂成儿子

考虑在 \(s\) 前加入两个不同字符,成为新字符串 \(t_1,t_2\),那么必定 \(\text{endpos}(t_1)\cap\text{endpos}(t_2)=\varnothing\),因为 \(\text{endpos}(t_1)\) 和 \(\text{endpos}(t_2)\) 必定不被包含。

所以,对于每个等价类集,最多分割出的大小不会超过原字符串集合大小。由于 \(\text{parent tree}\) 一共只有 \(n\) 个叶子结点,所以是 \(O(n)\) 的。


四. 后缀自动机的构建

后缀自动机的本质,是通过 \(\text{parent tree}\) 来维护的。

fSP1Cn.png

我们用 \(fa_u\) 表示在 \(\text{parent tree}\) 中 \(u\) 的直接父亲。

我们之前在 \(\text{parent tree}\) 已经知道了在子串前面加字符的做法,那么现在讨论关于在后面加字符的做法。

用 \(ch_{u,c}\) 表示在 \(u\) 结点对应的等价类最长子串后面加上一个字符 \(c\) ,形成的新字符串所属的等价类对应的结点。

也就是 \(\text{endpos(ch}_{u,c})=\text{endpos}(len_{(\text{endpos(u)})+'c'})\)

可能有点绕,举个例子:

abcabstrs 的等价类在 \(u\) 节点,我们给它加上 t,就变成了 abcabstrst,其等价类在 \(10\) 节点。那么 \(ch_{u,'t'-'a'}=v\)。

SAM 的一个合法的连边方案应该满足,从源点出发到达点 \(u\) 经过的边形成的字符串,应该是点 \(u\) 的等价类的一个字符串,也就意味着,形成的字符串应该是点 \(u\) 的字符串的后缀。

我们先以 AABAB 为例子。

由于单张图片上传大小太大,所以我直接画出整个自动机 AABAB。显然现在的自动机有误,待会解释。

下图蓝色表示边上转移的字符,灰色表示点。

首先将空节点 \(1\) 加入。

然后加入字符 A,即 \(2\) 号节点。

加入 AA,即 \(3\) 号节点。由于 AAA 的后缀,所以直接从 \(2\to 3\),转移为 A

\(3\) 的后缀:

A: 1->2
AA: 1->2->3

加入 AAB,即 \(4\) 号节点。先连接 \(3\to 4\),转移是 B。但是 \(1,2\) 号节点都没有字符为 B 的出边,于是连接 \(1\to4,2\to4\),转移是 B

\(4\) 的后缀:

B: 1->4
AB: 1->2->4
AAB: 1->2->3->4

加入 AABA,即 \(5\) 号节点。直接连接 \(4\to 5\)。

\(5\) 的后缀:

A: 1->2
BA: 1->4->5
ABA: 1->2->4->5
AABA: 1->2->3->4->5

加入 AABAB,此时与 \(4\) 时同理,连接 \(2\to 6,1\to 6\)。

\(6\) 的后缀:

B: 1->6
AB: 1->2->6
BAB: 1->4->6
ABAB: 1->2->4->5->6
AABAB: 1->2->3->4->5->6

检查一下这个自动机。从 \(1,2\) 号点出发,有两条字符为 B 的边,显然不符合 SAM 的性质。

我们考虑新建一个节点 \(7\),其字符串为 \(4,6\) 节点的最长公共后缀,即 AB

我们考虑关于 \(7\) 的连边。

首先 \(1\to7,2\to7\),转移为 \(B\)。然后连接 \(7\to5\),转移为 A。这样,保证了 \(5\) 也满足。

\(4,5,6\) 的后缀:

4:
B: 1->7
AB: 1->2->7
AAB: 1->2->3->4
5:
A: 1->2
BA: 1->7->5
ABA: 1->2->7->5
AABA: 1->2->3->4->5
6: 
B: 1->7
AB: 1->2->7
BAB: 1->7->5->6
ABAB: 1->2->7->5->6
AABAB: 1->2->3->4->5->6

我们看看 AABABA 的 SAM:

五. SAM 的代码实现

刚才我们已经模拟出了一个 SAM,现在我们考虑它如何用代码实现。

先放代码。代码中的 \(len,ch,fa\) 等如上文,只是用结构体表示第一维。

#include<bits/stdc++.h>
using namespace std;

const int N=2e6+3;

int n,last=1,tot=1,ans;
char s[N];

struct SAM{
	int len,fa;
	int ch[26];
	SAM(){memset(ch,0,sizeof(ch));len=0;}
}sam[N];

inline void add(int c){
	int p=last,np=++tot;
	last=np;
	sam[np].len=sam[p].len+1;
	while(p&&!sam[p].ch[c]){
		sam[p].ch[c]=np;
		p=sam[p].fa;
	}
	if(!p)sam[np].fa=1;
	else{
		int q=sam[p].ch[c];
		if(sam[q].len==sam[p].len+1)sam[np].fa=q;
		else{
			int nq=++tot;
			sam[nq]=sam[q];
			sam[nq].len=sam[p].len+1;
			sam[q].fa=sam[np].fa=nq;
			while(p&&sam[p].ch[c]==q){
				sam[p].ch[c]=nq;
				p=sam[p].fa;
			}
		}
	}
}

int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++)add(s[i]-'a');
	return 0;
}

一步一步讲解。


struct SAM{
	int len,fa;
	int ch[26];
	SAM(){memset(ch,0,sizeof(ch));len=0;}
}sam[N];

inline void add(int c){
	int p=last;
	np=++tot;
	last=np;
	sam[np].len=sam[p].len+1;
	//......
}

int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++)add(s[i]-'a');
	return 0;
}

构建 SAM 的方法是增量法,即 SAM 每次吞进一个字符,对于这个字符而改变内部结构,再吞进下一个字符,以此类推。

每次吞字符的行为之间需要传递一些信息,除了以上提到的 \(fa\) 和 \(ch\) 照例保留外,还需要传递一个 \(last\) 变量,表示上一次添加字符后的串 \(s\) 所属的等价类

显然,上一次加完字符后的串 \(s\) ,一定是 \(last\) 这个等价类的最长子串

所以在最长子串 \(s\) 后加上了一个字符 \(c\) 形成了一个新串 \(t\),根据引理 \(4\),\(t\) 将会归到一个新的等价类里。

所以我们开一个新点 \(np\) ,表示 \(t\) 所属的新等价类对应的结点。

\(\therefore len_{np}=len_p+1\)

struct SAM{
	int len,fa;
	int ch[26];
	SAM(){memset(ch,0,sizeof(ch));len=0;}
}sam[N];//将len、ch、fa存进结构体

inline void add(int c){
	int p=last;//当前最长子串所在的等价类
	np=++tot;//新等价类
	last=np;//更新 last
	sam[np].len=sam[p].len+1;//引理4得
	//......
}

int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	for(int i=1;i<=n;i++)add(s[i]-'a');//增量法
	return 0;
}

inline void add(int c){
	//......
	while(p&&!sam[p].ch[c]){
		sam[p].ch[c]=np;
		p=sam[p].fa;
	}
	//......
}

遍历新串后缀,到第一个出现过的子串为止。

每当我们加入一个新字符,可能之前某些子串的出现位置(即 \(\text{endpos}\) 中的元素)就会变多。

比如当前加到第 \(n\) 位,有些新子串从来没出现过,需要多开一个 \(\{n\}\) 的等价类包含它们;有些新子串曾经出现过,它们的 \(\text{endpos}\) 中就多一个 \(n\) 。

这一段就是找到这些新子串的出现位置。对于新子串 \(t\),它必然是加上新字符前的原字符串的某后缀,再拼接上我们现在加的这个字符 \(c\)。

既然是后缀关系,对于拼上 \(c\) 的新串 \(t\),一定存在某长度的后缀,使得长度小于它的其它后缀都出现过。

比如 ABCABSTRSTR,给它加上一个 S,那么 只有ABCABSTRSTRS,BCABSTRSTRS,CABSTRSTRS...TRS,RS,S 以及 \(\varnothing\) 的 \(\text{endpos}\) 会发生改变。

然而,在这些子串中,TRS,RS,S 和 \(\varnothing\) 在之前就出现过,所以它们在它们原有的 \(\text{endpos}\) 的基础上加上 \(11\);而其余的就直接归类于 \(11\)。

fSP1Cn.png

为了区分这些子串之前有没有出现过,考虑遍历后缀,找到这个第一个出现过的后缀。

又因为在一个字符串前面加一个字符等价于将其 \(\text{endpos}\) 分裂,即在 \(\text{parent tree}\) 上分裂出子树。

反过来看,相当于删去前面的字符,那么就相当于在 \(\text{parent tree}\) 上原地不动(不发生分裂)或者往上跳 \(fa\)。

所以通过跳 \(fa\) 来遍历后缀。

inline void add(int c){
	//......
	while(p&&!sam[p].ch[c]){//p的判断表示防止跳出parent tree;!sam[p].ch[c] 表示p+'c'的最长子串是否曾经出现过
		sam[p].ch[c]=np;//如果没有出现过,那么它的endpos直接为np
		p=sam[p].fa;//跳fa操作
	}
	//......
}

inline void add(int c){
	//...
	if(!p)sam[np].fa=1;
	else{
		int q=sam[p].ch[c];
		if(sam[q].len==sam[p].len+1)sam[np].fa=q;
		else{
			int nq=++tot;
			sam[nq]=sam[q];
			sam[nq].len=sam[p].len+1;
			sam[q].fa=sam[np].fa=nq;
			while(p&&sam[p].ch[c]==q){
				sam[p].ch[c]=nq;
				p=sam[p].fa;
			}
		}
	}
}

分三个部分。

  1. 根作为它的父节点
if(!p)sam[np].fa=1;

这种比较容易,从 \(p\) 向上爬,如果一直爬到了根还没有 break,那么说明没有节点能做它的 \(fa\),那么只有根可以,那么直接认根做父节点。


  1. \(len_q=len_p+1\)
int q=sam[p].ch[c];
else if(sam[q].len==sam[p].len+1)sam[np].fa=q;

设 \(q=sam_p.ch_c\),我们需要知道 \(p\) 的最长子串 \(+c\) 是否是 \(q\) 的最长子串。

换句话说:

\(p\) 在第一个有 \(c\) 边的祖先停下了,\(q\) 即为 \(p\) 的 \(c\) 出边到达的节点。

其中一种情况是 \(p\) 的最长子串 \(+c\) 就是 \(q\) 的最长子串。

因为这个串是最长子串,显然同等价类的其它子串都是它的后缀。所以它的后缀也会添加 \(\{n\}\)。

那么我们直接令这整个集合 \(q\) 成为 \(np\)(它对应 \(\{n\}\) 等价类)的 \(fa\) 即可。

举个例子:

fSP1Cn.png

比如现在到了 ABCABSTRS,要加入 T,\(np\) 的最长子串即为 ABCABSTRST,在等价类 \(\{10\}\)。

然后我们通过跳 \(fa\),算出了最长的曾经出现过的后缀 ST

那么 \(q\) 即为 \(sam_{p,'t'-'a'}\),然后这个等价类最长子串就是 ST,所以 \(fa_{np}=q\),也就是在 \(\text{parent tree}\) 上 STABCABSTRST 的父亲,也就是说 \(\{7,10\}\) 是 \(\{10\}\) 父亲。

int q=sam[p].ch[c];//取出q
else if(sam[q].len==sam[p].len+1)//q的最长子串=p的最长子串+c,即q的长度=p长度+1
sam[np].fa=q;//q成为np的fa


  1. \(len_q≠len_p+1\)
else{
	int nq=++tot;
	sam[nq]=sam[q];
	sam[nq].len=sam[p].len+1;
	sam[q].fa=sam[np].fa=nq;
	while(p&&sam[p].ch[c]==q){
		sam[p].ch[c]=nq;
		p=sam[p].fa;
	}
}

比如像 \(7\) 号节点。

显然 \(len_p+1\) 不总是一定为 \(len_q\)。

既然我们需要一个可以做 \(p\) 的父亲的点,那么我们就构造一个 \(u\) 的父节点,设它为 \(nq\)。

所以 \(nq\) 的最长子串就是 \(p\) 的最长子串 \(+c\)。

\(\therefore len_{nq}=len_p+1\)

\(nq\) 在分裂以前与 \(q\) 的差别在且仅在于 \(\text{endpos}\),而在后面加一个字符能转移到哪里,就不在 \(\text{endpos}\) 决定的范围内了。

考虑 \(ch\),它表示的是在后面加字符后归在的等价类,显然 \(ch_{nq}\) 和 \(ch_q\) 其实没有区别。那么直接令 \(nq\) 继承 \(q\) 的 \(ch\)。

最后是 \(fa\)。\(fa_q\) 之前和 \(q\) 在树上成父子关系,根据引理 \(4\),当时的 \(len_{fa_q}+1\) 必然等于 \(q\) 最小子串长度。

而 \(q\) 的最小子串在 \(nq\) 等价类里。

\(fa_{nq}=fa_q,fa_q=fa_{np}=nq\)

举个例子:

aabab 在加入第二个 b 时子串 ab 已经存在,所以 ab 的 \(\text{endpos}\) 集合变为 \(\{3,5\}\),这样原来第一个 b 表示 aab,ab,b,它们的 \(\text{endpos}\) 都是 \(3\),而现在只能表示 aab 的 \(\text{endpos}\) 是\(3\),而 ab,b 的 \(\text{endpos}\) 是 \(3,5\),所以需要一个新的点来维护,同时这样操作也保证了如果在后面加入 c,只会增加 c,bc,abc 的 \(\text{endpos}\) 而不会增加 aabc 的 \(\text{endpos}\)。

但是有些结点的 \(ch_c\) 指向的是 \(q\),而分裂后,这些 \(ch_{c}\) 需要指向 \(nq\)。

所以考虑对于 \(p\) 不断往上 \(fa\)。判断条件为 \(sam_{p,ch_c}=q\)。

即 \(q\) 的 \(\text{endpos}\) 不包含 \(\{n\}\),而 \(p\) 的最长子串 \(+c\) 的 \(\text{endpos}\) 包含 \(\{n\}\)。

所以令这条边连向新的节点 \(nq\),不断操作即可。

\(nq\) 的出边就是 \(q\) 的出边,然后 \(q\) 的所有除地面上地链上地入边都搬到了 \(nq\) 上面去。


这就讲完了代码部分。

六. 复杂度

七. 练习题目

给你一个长为 \(n\) 的字符串,求不同的子串的个数。

\(n\le 10^5\)

对于一个给定的长度为 \(n\) 的字符串,求出它的第 \(k\) 小子串。

\(1≤n≤5×10^5\)

标签:子串,SAM,sam,后缀,text,len,fa,endpos,自动机
From: https://www.cnblogs.com/trsins/p/17970738

相关文章

  • SA&SAM 小记
    0.Front纯笔记,不含教学内容,部分有拓展,部分太简单所以以”显然“带过了,总结了部分oi-wiki的内容。字符串为\(S\),长度为\(n\),且应有\(|\Sigma|\len\)。通常来说,大写字母表示为字符串,小写字母表示为字符。后缀的编号为\(i\),表示是以\(i\)为起点的后缀。基础小练习1.......
  • 浅谈 AC 自动机
    浅谈AC自动机前言这不是第一次看到这个算法。第一次是在OI-wiki上瞄到的。当时我还是一个什么也不懂的初一蒟蒻,看到这个名字就十分兴奋:“‘AC自动机’耶!是不是可以帮我自动AC!?”后来看到只是一个字符串算法,就离开了。今天上课讲了这个,感觉原理及实现没有后缀数组那么难......
  • AC 自动机学习笔记
    AC自动机学习笔记AC自动机可以用于解决字符串上的出现次数,出现位置问题。结合了Trie树和KMP的思想,在\(O(n)\)的时间内完成查询。相较于KMP的好处在于,AC自动机不仅速度快,而且支持多个模式串同时在一个文本串内查询。算法前置知识:Trie树,KMP,自动机基本概念。构建AC......
  • CodeForces 1266F Almost Same Distance
    洛谷传送门CF传送门好厉害。特判\(k=1\)。首先经过观察,我们可以按照\(k\)的奇偶性讨论:\(k\)为偶数,有一个中心点挂了若干条长度为\(\frac{k}{2}\)的链。\(k\)为偶数,有两个中心点,两边挂了若干条长度为\(\frac{k}{2}\)的链;\(k\)为奇数,有一个中心点挂了若干条长度......
  • 后缀数组 SA 学习笔记
    后缀数组SA学习笔记后缀数组处理字符串后缀排名,公共子串类问题十分优秀,可以在部分情况下替代后缀自动机(SAM),本文主要讲解后缀数组的实现过程和部分例题。正文定义后缀:从\(i\)开始到字符串结束的一个特殊子串,本文用\(suf(i)\)表示从\(i\)开始的后缀。后缀数组SA:SA是......
  • MYISAM和INNODB的区别
    INNODB支持事务,而MYISAM不支持事务。INNODB支持外键,而MYISAM不支持外键。MYISAM中B+Tree的数据结构存储的内容是实际数据的地址值,它的索引和实际数据是分开的,只不过使用索引指向了实际数据。这种索引的模式被称为非聚集索引。InnoDB中B+树的数据结构中存储的都是实......
  • 超级简单的后缀数组(SA)笔记!!
    超级简单的后缀数组(SA)!!(未完)前言这里选择当一手标题党。由于刚学完这个字符串算法,本人字符串算法又比较薄弱,好不容易这一次在晚修看各种资料看得七七八八,决定趁脑子清醒的时候记录下来。免得自己不久后忘了后又要痛苦地再看各种资料。希望这篇博客能帮到你。前置知识:RMQ问题......
  • 文件显示.[[email protected]].2700的后缀,中勒索病毒了
    勒索病毒是一种新型电脑病毒,主要通过邮件、程序木马、网页挂马等形式进行传播。一旦感染,它会利用各种加密算法对文件进行加密,被感染者一般无法解密,必须拿到解密的私钥才有可能破解。该病毒会修改壁纸,在桌面等明显位置生成勒索提示文件,指导用户去缴纳赎金。攻击的样本以exe、js、wsf......
  • 速通 形式语言与自动机
    有啥要学的?DFA/NFA的记号:\((Q,\Sigma,\delta,q_0,F)\)。NFA到DFA:子集构造(到\(2^n\)级别的构造:所有最后第\(n\)位为\(1\)的01串)。\(\varepsilon-\)NFA到DFA:类似地进行子集构造,每次转移时考虑对应\(\varepsilon-\)闭包。文本搜索:trie树(记得加上根到自身的\(\Si......
  • 后缀自动机(SAM)
    对OIWIKI进行了抄写删减精炼定义字符串\(s\)的SAM是一个接受\(s\)的所有后缀的最小DFA(确定性有限自动机或确定性有限状态自动机)。SAM是一张有向无环图。节点称作状态,边称作转移图存在一个源点\(t_0\),称作初始状态(即下图红点),其他节点均可从\(t_0\)出发到达转移......