关于广义后缀树(多串SAM)的总结

之前我们给的SAM的例题,基本上是一个串建SAM的就能做的

如果要建多个串的SAM应该怎么做呢

首先看题,bzoj2780

我一开始的想法是SA以前的弄法,把串拼起来,中间加分隔符做SAM

这题确实可以这么做,这样根据SAM能识别所有子串的性质

而且每个节点都代表了唯一的一个串

每个询问串我们都能找到最终转移到哪(找不到就是没出现过)

问在多少个串出现过这就等价于在ST(s)的parent树的子树中,出现了多少种不同的权值

这显然可以维护dfs序,用经典的离线做法来搞

  1 type node=record
  2        po,next:longint;
  3      end;
  4
  5 var go:array[0..300010,1..27] of longint;
  6     d,mx,fa,l,r,p,c,wh,w,fir,next:array[0..300010] of longint;
  7     ans,a,b:array[0..60010] of longint;
  8     e:array[0..300010] of node;
  9     h,t,k,last,len,i,j,n,q,x:longint;
 10     s,ss:ansistring;
 11
 12 function lowbit(x:longint):longint;
 13   begin
 14     exit(x and (-x));
 15   end;
 16
 17 function cmp(a,b:longint):boolean;
 18   begin
 19     exit(l[a]<l[b]);
 20   end;
 21
 22 procedure swap(var a,b:longint);
 23   var c:longint;
 24   begin
 25     c:=a;
 26     a:=b;
 27     b:=c;
 28   end;
 29
 30 procedure sort(l,r:longint);
 31   var i,j,x:longint;
 32   begin
 33     i:=l;
 34     j:=r;
 35     x:=a[(l+r) shr 1];
 36     repeat
 37       while cmp(a[i],x) do inc(i);
 38       while cmp(x,a[j]) do dec(j);
 39       if not(i>j) then
 40       begin
 41         swap(a[i],a[j]);
 42         swap(b[i],b[j]);
 43         inc(i);
 44         dec(j);
 45       end;
 46     until i>j;
 47     if l<j then sort(l,j);
 48     if i<r then sort(i,r);
 49   end;
 50
 51 procedure build(x,y:longint);
 52   begin
 53     inc(len);
 54     e[len].po:=y;
 55     e[len].next:=p[x];
 56     p[x]:=len;
 57   end;
 58
 59 procedure add(c,x:longint);
 60   var p,q,np,nq:longint;
 61   begin
 62     p:=last;
 63     inc(t); last:=t; np:=t;
 64     w[np]:=x; mx[np]:=mx[p]+1;
 65     while (p<>0) and (go[p,c]=0) do
 66     begin
 67       go[p,c]:=np;
 68       p:=fa[p];
 69     end;
 70     if p=0 then fa[np]:=1
 71     else begin
 72       q:=go[p,c];
 73       if mx[q]=mx[p]+1 then fa[np]:=q
 74       else begin
 75         inc(t); nq:=t;
 76         mx[nq]:=mx[p]+1;
 77         go[nq]:=go[q];
 78         fa[nq]:=fa[q];
 79         fa[q]:=nq; fa[np]:=nq;
 80         while go[p,c]=q do
 81         begin
 82           go[p,c]:=nq;
 83           p:=fa[p];
 84         end;
 85       end;
 86     end;
 87   end;
 88
 89 procedure dfs(x:longint);
 90   var i:longint;
 91   begin
 92     inc(h);
 93     l[x]:=h;
 94     d[h]:=w[x];  //dfs序
 95     i:=p[x];
 96     while i<>0 do
 97     begin
 98       dfs(e[i].po);
 99       i:=e[i].next;
100     end;
101     r[x]:=h;
102   end;
103
104 procedure work(x:longint);
105   begin
106     while x<=t do
107     begin
108       inc(c[x]);
109       x:=x+lowbit(x);
110     end;
111   end;
112
113 function ask(x:longint):longint;
114   begin
115     ask:=0;
116     while x>0 do
117     begin
118       ask:=ask+c[x];
119       x:=x-lowbit(x);
120     end;
121   end;
122
123 begin
124   readln(n,q);
125   for i:=1 to n do
126   begin
127     readln(ss);
128     len:=length(ss);
129     for j:=1 to len do  //拼接
130     begin
131       s:=s+ss[j];
132       inc(t); wh[t]:=i;
133     end;
134     if i<>n then
135     begin
136       s:=s+chr(97+26);
137       inc(t);
138     end;
139   end;
140   len:=length(s);
141   t:=1; last:=1;
142   for i:=len downto 1 do
143     add(ord(s[i])-96,wh[i]);
144
145   len:=0;
146   for i:=2 to t do  //构建树
147     build(fa[i],i);
148
149   dfs(1);
150   for i:=1 to q do
151   begin
152     readln(s);
153     j:=1;
154     len:=length(s);
155     for k:=len downto 1 do  //每个询问串最终转移到哪
156     begin
157       x:=ord(s[k])-96;
158       if go[j,x]=0 then
159       begin
160         j:=0;
161         break;
162       end
163       else j:=go[j,x];
164     end;
165     a[i]:=j;
166     b[i]:=i;
167   end;
168   for i:=t downto 2 do  //经典的离线做法
169   begin
170     next[i]:=fir[d[i]];
171     fir[d[i]]:=i;
172   end;
173   for i:=1 to n do
174     if fir[i]<>0 then work(fir[i]);
175   sort(1,q);
176   j:=1;
177   while a[j]=0 do inc(j);
178   for i:=1 to t do
179   begin
180     while (j<=q) and (l[a[j]]=i) do
181     begin
182       ans[b[j]]:=ask(r[a[j]])-ask(i-1);
183       inc(j);
184     end;
185     if d[i]<>0 then
186       if next[i]<>0 then work(next[i]);
187   end;
188   for i:=1 to q do
189     writeln(ans[i]);
190 end.

2780

然后我看到了bzoj3277 3473(双倍经验)

用我刚才的做法似乎不好搞,因为这题问每个字符串有多少子串出现在至少k个子串中

而刚才那种拼接,每个节点可接受的子串会搞出一堆不存在的子串

这时,我膜拜了wyfcyx的构建广义后缀树的做法,显然这里每个串要反序建SAM(这样才能构造出原串的后缀树)

他的做法是建完一个串的SAM后回到根,对下个串s先匹配

如果转移到的节点在SAM中可接受的最长串长度=当前匹配s的长度,那么这个节点可以代表s

否则的话就像SAM一样新开一个节点,感觉很(bu)奇(ming)妙(bai)

这样每个节点可能代表了多个串的子串,并且也没有多出来奇怪的子串

而且每个节点可接受串出现在多少个串中依然=parent树的子树中,出现了多少种不同的权值

这样我们可以像刚才一样,求出每个点出现次数,如果大于等于k,

那么根据之前的性质,这个点p可接受串的长度为[max[fa[p]]+1,max[p]]

那么点p能做出的贡献即为max[p]-max[fa[p]],否则贡献为0

由于子串是某个后缀的前缀,所以每个字符串的答案等于所有这个字符串的后缀节点的从根到该节点的权值和

  1 type node=record
  2        po,next:longint;
  3      end;
  4
  5 var e,w,pr:array[0..400010] of node;
  6     go:array[0..400010,‘a‘..‘z‘] of longint;
  7     sa,r,h,q,p,c,d,cur,a,b,mx,fa:array[0..400010] of longint;
  8     g,ans:array[0..400010] of int64;
  9     n,k,l,ti,y,f,i,j,len,x,t,last:longint;
 10     s:ansistring;
 11     ch:char;
 12
 13 function lowbit(x:longint):longint;
 14   begin
 15     exit(x and (-x));
 16   end;
 17
 18 procedure swap(var a,b:longint);
 19   var c:longint;
 20   begin
 21     c:=a;
 22     a:=b;
 23     b:=c;
 24   end;
 25
 26 procedure ins(x,y:longint);
 27   begin
 28     inc(len);
 29     e[len].po:=y;
 30     e[len].next:=p[x];
 31     p[x]:=len;
 32   end;
 33
 34 procedure add(c:char;x:longint);
 35   var p,q,np,nq:longint;
 36   begin
 37     p:=last;
 38     inc(t); last:=t; np:=t;
 39     mx[np]:=mx[p]+1;
 40     while (p<>0) and (go[p,c]=0) do
 41     begin
 42       go[p,c]:=np;
 43       p:=fa[p];
 44     end;
 45     if p=0 then fa[np]:=1
 46     else begin
 47       q:=go[p,c];
 48       if mx[q]=mx[p]+1 then fa[np]:=q
 49       else begin
 50         inc(t); nq:=t;
 51         mx[nq]:=mx[p]+1;
 52         go[nq]:=go[q];
 53         fa[nq]:=fa[q];
 54         fa[q]:=nq; fa[np]:=nq;
 55         while go[p,c]=q do
 56         begin
 57           go[p,c]:=nq;
 58           p:=fa[p];
 59         end;
 60       end;
 61     end;
 62   end;
 63
 64 procedure change(c:char);
 65   var p,np,q:longint;
 66   begin
 67     p:=go[last,c];
 68     if mx[p]=mx[last]+1 then last:=p
 69     else begin
 70       inc(t); np:=t;
 71       mx[np]:=mx[last]+1;
 72       go[np]:=go[p];
 73       fa[np]:=fa[p];
 74       fa[p]:=np;
 75       q:=last;
 76       while go[q,c]=p do
 77       begin
 78         go[q,c]:=np;
 79         q:=fa[q];
 80       end;
 81       last:=np;
 82     end;
 83   end;
 84
 85 procedure dfs(x:longint);
 86   var i:longint;
 87   begin
 88     inc(ti);
 89     sa[ti]:=x;   //dfs序上对应哪个点
 90     b[x]:=ti;
 91     i:=p[x];
 92     while i<>0 do
 93     begin
 94       dfs(e[i].po);
 95       i:=e[i].next;
 96     end;
 97     r[x]:=ti;
 98   end;
 99
100 procedure dfss(x:longint);
101   var i:longint;
102   begin
103     g[x]:=g[x]+g[fa[x]];
104     i:=p[x];
105     while i<>0 do
106     begin
107       dfss(e[i].po);
108       i:=e[i].next;
109     end;
110   end;
111
112 procedure work(x,w:longint);
113   begin
114     while x<=t do
115     begin
116       inc(c[x],w);
117       x:=x+lowbit(x);
118     end;
119   end;
120
121 function ask(x:longint):longint;
122   begin
123     ask:=0;
124     while x>0 do
125     begin
126       ask:=ask+c[x];
127       x:=x-lowbit(x);
128     end;
129   end;
130
131 procedure put(x,y:longint);
132   begin
133     inc(len);  //这个串x所有后缀所在的节点
134     w[len].po:=y;
135     w[len].next:=q[x];
136     q[x]:=len;
137     pr[len].po:=x;  //节点代表了哪些串的后缀
138     pr[len].next:=h[y];
139     h[y]:=len;
140   end;
141
142 function cmp(a,b:longint):boolean;
143   begin
144     exit(r[a]<r[b]);
145   end;
146
147 procedure sort(l,r:longint);
148   var i,j,x:longint;
149   begin
150     i:=l;
151     j:=r;
152     x:=a[(l+r) shr 1];
153     repeat
154       while cmp(a[i],x) do inc(i);
155       while cmp(x,a[j]) do dec(j);
156       if not(i>j) then
157       begin
158         swap(a[i],a[j]);
159         inc(i);
160         dec(j);
161       end;
162     until i>j;
163     if l<j then sort(l,j);
164     if i<r then sort(i,r);
165   end;
166
167 begin
168   readln(n,k);
169   last:=1; t:=1;
170   for i:=1 to n do
171   begin
172     readln(s);
173     l:=length(s); last:=1;
174     for j:=l downto 1 do
175     begin
176       if go[last,s[j]]<>0 then change(s[j]) //广义后缀树
177       else add(s[j],i);
178       put(i,last);
179     end;
180   end;
181   len:=0;
182   for i:=2 to t do
183     ins(fa[i],i);
184
185   dfs(1);
186   for i:=1 to t do
187     a[i]:=i;
188   sort(1,t);
189   j:=1; x:=a[1];
190   for i:=1 to t do
191   begin
192     f:=h[sa[i]];
193     while f<>0 do  //因为一个节点可能代表了多个穿,插入相对麻烦
194     begin
195       y:=pr[f].po;
196       if cur[y]<>0 then work(cur[y],-1);
197       cur[y]:=i;
198       work(i,1);
199       f:=pr[f].next;
200     end;
201     while (j<=t) and (r[x]=i) do
202     begin
203       len:=ask(i)-ask(b[x]-1);
204       if len<k then g[x]:=0 else g[x]:=mx[x]-mx[fa[x]];
205       inc(j); x:=a[j];
206     end;
207     if j=t+1 then break;
208   end;
209   dfss(1);
210   for i:=1 to n do
211   begin
212     j:=q[i];
213     while j<>0 do
214     begin
215       x:=w[j].po;
216       ans[i]:=ans[i]+g[x];
217       j:=w[j].next;
218     end;
219   end;
220   for i:=1 to n do
221     write(ans[i],‘ ‘);
222   writeln;
223 end.

bzoj2806 第一道小强和阿米巴的题,解锁新成就

构造出标准作文库的SAM后,L0不难想到二分答案吧

然后我们可以求出以询问串每个位置i为结尾的最长子串长度P[i]

不难得到f[i]到i最长熟悉 f[i]=max(f[i-1],f[j]+i-j) (i-j>=l0 且 (i-j<=P[i])

然后这个是明显的单调队列优化吧

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

2806

时间: 2024-08-29 20:15:24

关于广义后缀树(多串SAM)的总结的相关文章

广义后缀树(GST)算法的简介

导言 最近软件安全课上,讲病毒特征码的提取时,老师讲了一下GST算法. 这里就做个小总结. 正文 广义后缀树的英文为Generalized Suffix Tree,简称GST. GST算法的提出是为了解决最大公共子串问题,也就是在多个字符串中,找到他们共有的子串.这个问题听起来和最大公共子序列问题(LCS)有些相似,但是二者有两个不同点:①一个是公共子串,一个是公共子序列,后者可以是不连续的:②GST算法可以对多个字符串求公共子串,而我们一般指的LCS算法只能对两个字符串求公共子序列. 下面继续

【XSY1551】往事 广义后缀数组 线段树合并

题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\)最长公共前缀,\(LCS=\)最长公共后缀 \(1\leq n\leq 200000\),字符集为\(\{0\ldots 300\}\) 题解 我们先看看这个\(LCP(s_u,s_v)\)怎么求 广义后缀自动机不行.广义后缀树可能可以,但我不会.广义后缀数组可以.然后我就开始手推广义后缀数组 广义

【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r$ 中的哪一个里出现次数最多,输出出现次数最多的串编号(如果有多个则输出编号最小的)以及相应出现次数. $|S|,q\le 5\times 10^5$ ,$\sum\limits_{i=1}^m|T_i|\le 5\times 10^4$ . 题解 广义后缀自动机+树上倍增+线段树合并 对 $S$

[转载]字典树(trie树)、后缀树

(1)字典树(Trie树) Trie是个简单但实用的数据结构,通常用于实现字典查询.我们做即时响应用户输入的AJAX搜索框时,就是Trie开始.本质上,Trie是一颗存储多个字符串的树.相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串.和普通树不同的地方是,相同的字符串前缀共享同一条分支.还是例子最清楚.给出一组单词,inn, int, at, age, adv, ant, 我们可以得到下面的Trie: 可以看出: 每条边对应一个字母. 每个节点对应一项前

【UOJ131/NOI2015D2T2-品酒大会】sam求后缀树

题目链接:http://uoj.ac/problem/131 题意:给出一个字符串,第i个字符对应的值为a[i], 对于i∈[0,n),求最长公共前缀大于等于i的字串对个数,并求这些字符串对开头对应值相乘最大值.n=3*10^5 题解: 学了个厉害的东西啊... 正解好像是sa+并查集(合并height) 然而我学了个用sam的做法.. 对于第一问: 首先我们要知道,建立后缀自动机之后,parent树就是逆序串的后缀树. why?看这个博客好了:http://z55250825.blog.163

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

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

康复计划#1 再探后缀自动机&amp;后缀树

本篇口胡写给我自己这样的东西都忘光的残废选手 以及那些刚学SAM,看了其他的一些东西并且没有完全懵逼的人 (初学者还是先去看有图的教程吧,虽然我的口胡没那么好懂,但是我觉得一些细节还是讲清楚了的) 大概是重复一些有用的想法和性质,用以加深印象吧-如果可以的话希望也能理解得更透彻一点- 1.如何设计出一个后缀自动机? 现在用的SAM并不是本来就在那里的,要比较深入地理解,就不能只从验证它对不对的角度考虑,而要考虑为什么它是这个样子. 要一个能够接受后缀的有限状态机,并不用像现在的SAM那样弄,比如

BZOJ 3277 串 (广义后缀自动机)

3277: 串 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 309 Solved: 118 [Submit][Status][Discuss] Description 字符串是oi界常考的问题.现在给定你n个字符串,询问每个字符串有多少子串(不包括空串)是所有n个字符串中至少k个字符串的子串(注意包括本身). Input 第一行两个整数n,k. 接下来n行每行一个字符串. Output 输出一行n个整数,第i个整数表示第i个字符串的答案. Sa

广义后缀自动机

1).自动机的介绍 首先我们先来介绍一下什么是自动机,有限状态自动机的功能是识别字符串,令一个自动机A,若他能识别字符串S,就记为A(S)=Ture,否则A(S)=False. 自动机由五个部分组成,alpha:字符集,state:状态集合,init:初始状态,end:结束状态集合,trans:状态转移函数. 令trans(s,ch)表示当前状态是s,在读入字符ch之后,所到达的状态.如果trans(s,ch)这个转移不存在,为了方便,设其为null,同时null只能转移到null.null表示