学习请看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.