关于后缀自动机的总结

学习请看clj冬令营的讲稿吧,我是一点一点慢慢啃的……

这里简单的说一下SAM的几个重要的性质

1.在SAM上,起点到任意一点的所有路径无重复的识别了所有子串

2.每个子串s出现的此处即ST(s)的right集合的大小(clj ppt中的定义)

具体的就是parent树上子树right集合的并(其实就是子树叶子节点个数)

3.(这是我认为最美妙的性质)SAM所构建出来的parent树与对原串逆序所购建出来的后缀树节点一一对应

具体的,SAM上每个节点最长接受子串=根节点到后缀树上每个点路径对应的字符串的逆序

看图,(吐槽一下fhq博客的回文串)

一目了然了吧,正是有SAM在构造的过程中把一棵后缀树构造出来了,所以后缀数组能做的题目基本上SAM也能做

而且很多跟子串有关的题目,运用SAM明显比较简单

下面随便扯几个题目

bzoj3998 运用性质2,然后在拓扑图上维护一个前缀和即可

  1 var mx,sa,fa:array[0..1000010] of longint;
  2     sum,w:array[0..1000010] of int64;
  3     go:array[0..1000010,‘a‘..‘z‘] of longint;
  4     i,n,ty,k,x,j,t,last:longint;
  5     s:ansistring;
  6     c:char;
  7
  8 procedure add(c:char);
  9   var p,np,nq,q:longint;
 10   begin
 11     inc(t);  p:=last;
 12     last:=t; np:=t; w[np]:=1;
 13     mx[np]:=mx[p]+1;
 14     while (p<>0) and (go[p,c]=0) do
 15     begin
 16       go[p,c]:=np;
 17       p:=fa[p];
 18     end;
 19     if p=0 then fa[np]:=1
 20     else begin
 21       q:=go[p,c];
 22       if mx[p]+1=mx[q] then fa[np]:=q
 23       else begin
 24         inc(t); nq:=t;
 25         mx[nq]:=mx[p]+1;
 26         go[nq]:=go[q];
 27         fa[nq]:=fa[q];
 28         fa[q]:=nq; fa[np]:=nq;
 29         while go[p,c]=q do
 30         begin
 31           go[p,c]:=nq;
 32           p:=fa[p];
 33         end;
 34       end;
 35     end;
 36   end;
 37
 38 procedure pre;
 39   var c:char;
 40   begin
 41     for i:=1 to t do
 42       inc(sum[mx[i]]);
 43     for i:=1 to n do
 44       inc(sum[i],sum[i-1]);
 45     for i:=t downto 1 do
 46     begin
 47       sa[sum[mx[i]]]:=i;
 48       dec(sum[mx[i]]);
 49     end;
 50     fillchar(sum,sizeof(sum),0);
 51     for i:=t downto 1 do
 52     begin
 53       x:=sa[i];
 54       if ty=1 then w[fa[x]]:=w[fa[x]]+w[x]
 55       else w[x]:=1;
 56     end;
 57     w[1]:=0;
 58     for i:=t downto 1 do
 59     begin
 60       x:=sa[i];
 61       sum[x]:=w[x];
 62       for c:=‘a‘ to ‘z‘ do
 63         sum[x]:=sum[x]+sum[go[x,c]];
 64     end;
 65   end;
 66
 67 procedure get(x,k:longint);
 68   var c:char;
 69       y:longint;
 70   begin
 71     while true do
 72     begin
 73       if k<=w[x] then exit;
 74       k:=k-w[x];
 75       for c:=‘a‘ to ‘z‘ do
 76       begin
 77         y:=go[x,c];
 78         if y<>0 then
 79         begin
 80           if k<=sum[y] then
 81           begin
 82             write(c);
 83             x:=y;
 84             break;
 85           end;
 86           k:=k-sum[y];
 87         end;
 88       end;
 89     end;
 90   end;
 91
 92 begin
 93   readln(s);
 94   n:=length(s);
 95   last:=1; t:=1;
 96   for i:=1 to n do
 97     add(s[i]);
 98   readln(ty,k);
 99   pre;
100   if k>sum[1] then writeln(-1)
101   else get(1,k);
102 end.

3998

bzoj3676 先用manacher找出所有本质不同回文串,然后放到SAM上跑即可

怎么跑?当然不是一个个匹配啦,把一个子串当作某个后缀的前缀,然后找到后缀对应的节点

然后倍增往上提~

(听说有个东西叫回文自动机?……)

  1 var go:array[0..600010,‘a‘..‘z‘] of longint;
  2     sa,sum,d,fa,mx:array[0..600010] of longint;
  3     p,loc:array[0..310000] of longint;
  4     anc:array[0..600010,0..18] of longint;
  5     w:array[0..600010] of int64;
  6     s:array[0..310000] of char;
  7     last,t,n:longint;
  8     ans:int64;
  9
 10 function min(a,b:longint):longint;
 11   begin
 12     if a>b then exit(b) else exit(a);
 13   end;
 14
 15 function max(a,b:int64):int64;
 16   begin
 17     if a>b then exit(a) else exit(b);
 18   end;
 19
 20 procedure add(c:char);
 21   var p,q,np,nq:longint;
 22   begin
 23     p:=last;
 24     inc(t); last:=t; np:=t;
 25     w[np]:=1; mx[np]:=mx[p]+1;
 26     while (p<>0) and (go[p,c]=0) do
 27     begin
 28       go[p,c]:=np;
 29       p:=fa[p];
 30     end;
 31     if p=0 then fa[np]:=1
 32     else begin
 33       q:=go[p,c];
 34       if mx[q]=mx[p]+1 then fa[np]:=q
 35       else begin
 36         inc(t); nq:=t;
 37         mx[nq]:=mx[p]+1;
 38         go[nq]:=go[q];
 39         fa[nq]:=fa[q];
 40         fa[q]:=nq; fa[np]:=nq;
 41         while go[p,c]=q do
 42         begin
 43           go[p,c]:=nq;
 44           p:=fa[p];
 45         end;
 46       end;
 47     end;
 48   end;
 49
 50 procedure prework;
 51   var x,y,i,j:longint; c:char;
 52   begin
 53     for i:=1 to t do
 54       inc(sum[mx[i]]);
 55     for i:=1 to n do
 56       inc(sum[i],sum[i-1]);
 57     for i:=t downto 1 do
 58     begin
 59       sa[sum[mx[i]]]:=i;
 60       dec(sum[mx[i]]);
 61     end;
 62     for i:=t downto 1 do
 63     begin
 64       x:=sa[i];
 65       w[fa[x]]:=w[fa[x]]+w[x];
 66     end;
 67     for i:=1 to t do
 68     begin
 69       x:=sa[i];
 70       d[x]:=d[fa[x]]+1;
 71       anc[x,0]:=fa[x];
 72       for j:=1 to 18 do
 73       begin
 74         y:=anc[x,j-1];
 75         if anc[y,j-1]<>0 then anc[x,j]:=anc[y,j-1] else break;
 76       end;
 77     end;
 78   end;
 79
 80 procedure getchar;
 81   var ch:char;
 82   begin
 83     read(ch);
 84     while (ch>=‘a‘) and (ch<=‘z‘) do
 85     begin
 86       inc(n);
 87       s[n]:=ch;
 88       add(ch);
 89       loc[n]:=last;
 90       read(ch);
 91     end;
 92   end;
 93
 94 procedure find(l,r:longint);
 95   var x,i:longint;
 96   begin
 97     x:=loc[r];
 98     for i:=18 downto 0 do
 99       if mx[anc[x,i]]>=r-l+1 then x:=anc[x,i];
100     ans:=max(ans,int64(r-l+1)*w[x]);
101   end;
102
103 procedure manacher;
104   var i,k,right:longint;
105   begin
106     right:=0; k:=0;
107     for i:=1 to n do
108     begin
109       if right>i then p[i]:=min(right-i,p[2*k-i])
110       else begin
111         p[i]:=1;
112         find(i-p[i]+1,i+p[i]-1);
113       end;
114       while s[i+p[i]]=s[i-p[i]] do
115       begin
116         inc(p[i]);
117         find(i-p[i]+1,i+p[i]-1);
118       end;
119       if p[i]+i>right then
120       begin
121         k:=i;
122         right:=p[i]+i;
123       end;
124     end;
125     right:=0; k:=0;
126     for i:=1 to n do
127     begin
128       if right>i then p[i]:=min(right-i,p[2*k-i-1])
129       else p[i]:=0;
130       while s[i+p[i]+1]=s[i-p[i]] do
131       begin
132         inc(p[i]);
133         find(i-p[i]+1,i+p[i]);
134       end;
135       if p[i]+i>right then
136       begin
137         k:=i;
138         right:=p[i]+i;
139       end;
140     end;
141   end;
142
143 begin
144   last:=1; t:=1;
145   getchar;
146   s[0]:=‘#‘; s[n+1]:=‘$‘;
147   prework;
148   manacher;
149   writeln(ans);
150 end.

3676

bzoj1396 2865(注意2865数据规模大了,SAM注意卡空间)

注意出现一次的子串s ST(s)一定是parent树上的叶子节点

clj ppt中有一个性质,一个节点p可接受的子串长度=[min(s),max(s)]=[max(fa[p])+1,max(s)]

所以我们反序构造SAM,叶子节点代表了一个后缀i

这样我们就可以更新,对于[i,i+max(fa[s])+1] 我们用max(fa[s])+1更新

对于j属于[i+max(fa[s])+2,n]我们用j-i更新

相当于一个线段覆盖问题,只不过一种线段斜率为0,一种线段斜率为1,我们可以分别用线段树维护

  1 const inf=100000007;
  2 var go:array[0..1000010,‘a‘..‘z‘] of longint;
  3     mx,fa,w:array[0..1000010] of longint;
  4     v:array[0..1000010] of boolean;
  5     tree:array[0..500010*3,0..1] of longint;
  6     i,n,t,last:longint;
  7     s:ansistring;
  8
  9 function min(a,b:longint):longint;
 10   begin
 11     if a>b then exit(b) else exit(a);
 12   end;
 13
 14 procedure add(c:char);
 15   var p,q,np,nq:longint;
 16   begin
 17     p:=last;
 18     inc(t); last:=t; np:=t;
 19     mx[np]:=mx[p]+1;
 20     while (p<>0) and (go[p,c]=0) do
 21     begin
 22       go[p,c]:=np;
 23       p:=fa[p];
 24     end;
 25     if p=0 then fa[np]:=1
 26     else begin
 27       q:=go[p,c];
 28       if mx[q]=mx[p]+1 then fa[np]:=q
 29       else begin
 30         inc(t); nq:=t;
 31         mx[nq]:=mx[p]+1;
 32         go[nq]:=go[q];
 33         fa[nq]:=fa[q];
 34         fa[q]:=nq; fa[np]:=nq;
 35         while go[p,c]=q do
 36         begin
 37           go[p,c]:=nq;
 38           p:=fa[p];
 39         end;
 40       end;
 41     end;
 42   end;
 43
 44 procedure build(i,l,r:longint);
 45   var m:longint;
 46   begin
 47     tree[i,0]:=inf;
 48     tree[i,1]:=inf;
 49     if l<>r then
 50     begin
 51       m:=(l+r) shr 1;
 52       build(i*2,l,m);
 53       build(i*2+1,m+1,r);
 54     end;
 55   end;
 56
 57 procedure cov(i,q,z:longint);
 58   begin
 59     tree[i,q]:=min(tree[i,q],z);
 60   end;
 61
 62 procedure push(i,q:longint);
 63   begin
 64     cov(i*2,q,tree[i,q]);
 65     cov(i*2+1,q,tree[i,q]);
 66     tree[i,q]:=inf;
 67   end;
 68
 69 procedure work(i,l,r,q,x,y,z:longint);
 70   var m:longint;
 71   begin
 72     if (x<=l) and (y>=r) then cov(i,q,z)
 73     else begin
 74       if tree[i,q]<inf then push(i,q);
 75       m:=(l+r) shr 1;
 76       if x<=m then work(i*2,l,m,q,x,y,z);
 77       if y>m then work(i*2+1,m+1,r,q,x,y,z);
 78     end;
 79   end;
 80
 81 procedure ask(i,l,r:longint);
 82   var m:longint;
 83   begin
 84     if l=r then writeln(min(n,min(tree[i,0],tree[i,1]+l)))
 85     else begin
 86       if tree[i,0]<inf then push(i,0);
 87       if tree[i,1]<inf then push(i,1);
 88       m:=(l+r) shr 1;
 89       ask(i*2,l,m);
 90       ask(i*2+1,m+1,r);
 91     end;
 92   end;
 93
 94 begin
 95   readln(s);
 96   n:=length(s);
 97   t:=1; last:=1;
 98   for i:=n downto 1 do
 99   begin
100     add(s[i]);
101     w[last]:=i;
102   end;
103   for i:=1 to t do
104     v[fa[i]]:=true;
105
106   build(1,1,n);
107   for i:=1 to t do
108     if not v[i] then
109     begin
110       work(1,1,n,0,w[i],w[i]+mx[fa[i]],mx[fa[i]]+1);
111       if w[i]+mx[fa[i]]<n then
112         work(1,1,n,1,w[i]+mx[fa[i]]+1,n,-w[i]+1);
113     end;
114   ask(1,1,n);
115 end.

2865

bzoj2946 如果用SA做,是经典的二分然后对height分组

用SAM怎么做呢?我们可以先把一个串的SAM建出来,然后用其它串匹配

得出每个节点在这个串匹配的最大长度,然后可以得到每个节点在所有串匹配的最大值的最小值

然后取所有节点这个值的最大值即可(很拗口,但是很明显)

  1 var go:array[0..20010,‘a‘..‘z‘] of longint;
  2     sum,mx,fa,f,a,sa:array[0..20010] of longint;
  3     ans,m,last,t,i,n,l,j,p,len:longint;
  4     s:ansistring;
  5
  6 function min(a,b:longint):longint;
  7   begin
  8     if a>b then exit(b) else exit(a);
  9   end;
 10
 11 function max(a,b:longint):longint;
 12   begin
 13     if a>b then exit(a) else exit(b);
 14   end;
 15
 16 procedure add(c:char);
 17   var p,q,np,nq:longint;
 18   begin
 19     p:=last;
 20     inc(t); last:=t; np:=t;
 21     mx[np]:=mx[p]+1;
 22     while (p<>0) and (go[p,c]=0) do
 23     begin
 24       go[p,c]:=np;
 25       p:=fa[p];
 26     end;
 27     if p=0 then fa[np]:=1
 28     else begin
 29       q:=go[p,c];
 30       if mx[q]=mx[p]+1 then fa[np]:=q
 31       else begin
 32         inc(t); nq:=t;
 33         mx[nq]:=mx[p]+1;
 34         go[nq]:=go[q];
 35         fa[nq]:=fa[q];
 36         fa[q]:=nq; fa[np]:=nq;
 37         while go[p,c]=q do
 38         begin
 39           go[p,c]:=nq;
 40           p:=fa[p];
 41         end;
 42       end;
 43     end;
 44   end;
 45
 46 procedure pre;
 47   var i:longint;
 48   begin
 49     for i:=1 to t do
 50     begin
 51       inc(sum[mx[i]]);
 52       f[i]:=mx[i];
 53     end;
 54     for i:=1 to n do
 55       inc(sum[i],sum[i-1]);
 56     for i:=t downto 1 do
 57     begin
 58       sa[sum[mx[i]]]:=i;
 59       dec(sum[mx[i]]);
 60     end;
 61   end;
 62
 63 begin
 64   readln(m);
 65   readln(s);
 66   n:=length(s);
 67   last:=1; t:=1;
 68   for i:=1 to n do
 69     add(s[i]);
 70   pre;
 71   for i:=2 to m do
 72   begin
 73     readln(s);
 74     l:=length(s);
 75     p:=1; len:=0;
 76     fillchar(a,sizeof(a),0);
 77     for j:=1 to l do
 78     begin
 79       while (p<>0) and (go[p,s[j]]=0) do p:=fa[p];
 80       if p=0 then
 81       begin
 82         p:=1;
 83         len:=0;
 84       end
 85       else begin
 86         len:=min(len,mx[p])+1;
 87         p:=go[p,s[j]];
 88       end;
 89       a[p]:=max(a[p],len);
 90     end;
 91     for j:=t downto 1 do
 92     begin
 93       p:=sa[j];
 94       a[fa[p]]:=max(a[fa[p]],a[p]);
 95     end;
 96     for j:=1 to t do
 97       f[j]:=min(f[j],a[j]);
 98   end;
 99   for i:=1 to t do
100     ans:=max(ans,f[i]);
101   writeln(ans);
102 end.

时间: 2024-11-05 13:41:09

关于后缀自动机的总结的相关文章

hiho一下第128周 后缀自动机二&#183;重复旋律5

#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中出现了多少不同的旋律? 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串.字符串长度不超过 1000000. 输出 一行一个整数,表示答案. 样例输入 aab 样例输出 5 解题方法提示 小Hi:本周的题目其实就是给定一个字符串S,要求出S的所有不同子串的数

后缀自动机总结

后缀自动机是一种确定性有限自动机(DFA),它可以且仅可以匹配一个给定串的任意后缀. 构造一个可以接受一个给定串的所有后缀的不确定性有限自动机(NFA)是很容易的,我们发现我们用通用的将NFA转换成对应DFA的算法转换出来的DFA的状态数都很小(O(n)级别的,远远达不到指数级别).于是,人们就开始研究这种特殊的NFA,并提出了在线增量算法,用O(n)的时间复杂度构造该NFA的DFA.在转换过程中,DFA中对应的NFA中的状态集合其实就是我们的right集合.——————以上在胡扯———————

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp

如何优雅的研究 RGSS3 番外(一) ruby 实现的后缀自动机

*我真的不会 ruby 呀* #encoding:utf-8 #============================================================================== # ■ Suffix_Automaton #------------------------------------------------------------------------------ # 后缀自动机. #============================

【BZOJ3926】[Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

[BZOJ3926][Zjoi2015]诸神眷顾的幻想乡 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来.也就是说,这n块空地形成了一个树的结构. 有n个粉丝们来到了太阳花田上.为了表达对幽香生日的祝

【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四&#183;重复旋律7

解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? 小Ho:这道题目既然是关于子串,那么我知道从后缀自动机的所有状态中包含的子串的集合恰好对应原串的所有不重复子串. 小Hi:很好.那你可以先简化问题,想想只有一个串怎么做? 小Ho:好的.这个难不倒我.我上次已经知道如何计算一个串所有不同子串的数量,现在这题也类似,只不过计算更加复杂一点. 小Hi:

BZOJ3926 ZJOI2015 诸神眷顾的幻想乡 后缀自动机+DFS

题意:给定一颗字符树,求树中路径所构成的不同的字符串的数量,其中AB和BA视作不同的字符串 题解: 题目里有这样一句话:太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个. 一共有10W个点,却只有20个叶子……因此树上所有的字串就是以叶子为起点搜索出的所有字串,丽洁姐真的好善良啊- -(无雾) 这样从每个点开始就能跑出来一颗Trie树,对Trie构造广义后缀自动机——每个节点看成是一个根,在后面加字符的时候和普通的SAM一样. 然后在SAM上用DFS统计不同字串的数量即可 #inc

SPOJ 1812 Longest Common Substring II(后缀自动机)

[题目链接] http://www.spoj.com/problems/LCS2/ [题目大意] 求n个串的最长公共子串 [题解] 对一个串建立后缀自动机,剩余的串在上面跑,保存匹配每个状态的最小值, 取最小值中的最大值即可.由于跑的地方只记录了匹配结尾的状态, 所以还需要更新parent树上的状态,既然匹配到了子节点, 那么parent树链上的值就都能够取到l, 一开始给每个不同状态按照l从小到大分配储存地址, 这样,我们就可以从匹配长度最长的开始更新parent树的情况. [代码] #inc

hdu5853 (后缀自动机)

Problem Jong Hyok and String 题目大意 给你n个字符串,有q个询问. 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t的数量. (n,q<=10^5 , 字符串总长<=10^5) 解题分析 直接将n个串塞进一个后缀自动机里面. 对于一个询问串qi,找到其在后缀自动机中出现的位置j. 则答案为len[j] - len[fail[j]] . (具体为什么还需要好好斟酌一下) 参考程序 1

后缀自动机专题

如果不算pre指针的话后缀自动机就是一个DAG,这是它能很方便地进行dp的前提. 而pre指针返回什么呢,返回的就是上一个的前缀包含改结点所代表子串的那个后缀,和AC自动机上的fail指针很像,都是为了匹配.我目前学得不深,看不出和AC自动机的fail指针有什么区别,用起来也几乎一样. 相比于字典树和回文树,后缀自动机每个结点会有多个父结点,可以表示多种子串(从根节点到它的每条路径都是一个子串),因此子串的信息只能在路径中记录,比如长度,而该子串说记录的长度step,则是根结点到它的最远距离,而