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

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

首先看题,bzoj2780

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

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

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

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

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

这显然可以维护dfs序,用经典的离线做法来搞(更好的写法见文末UPD)

 type node=record
po,next:longint;
end; var go:array[..,..] of longint;
d,mx,fa,l,r,p,c,wh,w,fir,next:array[..] of longint;
ans,a,b:array[..] of longint;
e:array[..] of node;
h,t,k,last,len,i,j,n,q,x:longint;
s,ss:ansistring; function lowbit(x:longint):longint;
begin
exit(x and (-x));
end; function cmp(a,b:longint):boolean;
begin
exit(l[a]<l[b]);
end; procedure swap(var a,b:longint);
var c:longint;
begin
c:=a;
a:=b;
b:=c;
end; procedure sort(l,r:longint);
var i,j,x:longint;
begin
i:=l;
j:=r;
x:=a[(l+r) shr ];
repeat
while cmp(a[i],x) do inc(i);
while cmp(x,a[j]) do dec(j);
if not(i>j) then
begin
swap(a[i],a[j]);
swap(b[i],b[j]);
inc(i);
dec(j);
end;
until i>j;
if l<j then sort(l,j);
if i<r then sort(i,r);
end; procedure build(x,y:longint);
begin
inc(len);
e[len].po:=y;
e[len].next:=p[x];
p[x]:=len;
end; procedure add(c,x:longint);
var p,q,np,nq:longint;
begin
p:=last;
inc(t); last:=t; np:=t;
w[np]:=x; mx[np]:=mx[p]+;
while (p<>) and (go[p,c]=) do
begin
go[p,c]:=np;
p:=fa[p];
end;
if p= then fa[np]:=
else begin
q:=go[p,c];
if mx[q]=mx[p]+ then fa[np]:=q
else begin
inc(t); nq:=t;
mx[nq]:=mx[p]+;
go[nq]:=go[q];
fa[nq]:=fa[q];
fa[q]:=nq; fa[np]:=nq;
while go[p,c]=q do
begin
go[p,c]:=nq;
p:=fa[p];
end;
end;
end;
end; procedure dfs(x:longint);
var i:longint;
begin
inc(h);
l[x]:=h;
d[h]:=w[x]; //dfs序
i:=p[x];
while i<> do
begin
dfs(e[i].po);
i:=e[i].next;
end;
r[x]:=h;
end; procedure work(x:longint);
begin
while x<=t do
begin
inc(c[x]);
x:=x+lowbit(x);
end;
end; function ask(x:longint):longint;
begin
ask:=;
while x> do
begin
ask:=ask+c[x];
x:=x-lowbit(x);
end;
end; begin
readln(n,q);
for i:= to n do
begin
readln(ss);
len:=length(ss);
for j:= to len do //拼接
begin
s:=s+ss[j];
inc(t); wh[t]:=i;
end;
if i<>n then
begin
s:=s+chr(+);
inc(t);
end;
end;
len:=length(s);
t:=; last:=;
for i:=len downto do
add(ord(s[i])-,wh[i]); len:=;
for i:= to t do //构建树
build(fa[i],i); dfs();
for i:= to q do
begin
readln(s);
j:=;
len:=length(s);
for k:=len downto do //每个询问串最终转移到哪
begin
x:=ord(s[k])-;
if go[j,x]= then
begin
j:=;
break;
end
else j:=go[j,x];
end;
a[i]:=j;
b[i]:=i;
end;
for i:=t downto do //经典的离线做法
begin
next[i]:=fir[d[i]];
fir[d[i]]:=i;
end;
for i:= to n do
if fir[i]<> then work(fir[i]);
sort(,q);
j:=;
while a[j]= do inc(j);
for i:= to t do
begin
while (j<=q) and (l[a[j]]=i) do
begin
ans[b[j]]:=ask(r[a[j]])-ask(i-);
inc(j);
end;
if d[i]<> then
if next[i]<> then work(next[i]);
end;
for i:= to q do
writeln(ans[i]);
end.

2780

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

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

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

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

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

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

否则的话就像SAM一样新开一个节点,感觉和可持久化的思想很像

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

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

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

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

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

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

 type node=record
po,next:longint;
end; var e,w,pr:array[..] of node;
go:array[..,'a'..'z'] of longint;
sa,r,h,q,p,c,d,cur,a,b,mx,fa:array[..] of longint;
g,ans:array[..] of int64;
n,k,l,ti,y,f,i,j,len,x,t,last:longint;
s:ansistring;
ch:char; function lowbit(x:longint):longint;
begin
exit(x and (-x));
end; procedure swap(var a,b:longint);
var c:longint;
begin
c:=a;
a:=b;
b:=c;
end; procedure ins(x,y:longint);
begin
inc(len);
e[len].po:=y;
e[len].next:=p[x];
p[x]:=len;
end; procedure add(c:char;x:longint);
var p,q,np,nq:longint;
begin
p:=last;
inc(t); last:=t; np:=t;
mx[np]:=mx[p]+;
while (p<>) and (go[p,c]=) do
begin
go[p,c]:=np;
p:=fa[p];
end;
if p= then fa[np]:=
else begin
q:=go[p,c];
if mx[q]=mx[p]+ then fa[np]:=q
else begin
inc(t); nq:=t;
mx[nq]:=mx[p]+;
go[nq]:=go[q];
fa[nq]:=fa[q];
fa[q]:=nq; fa[np]:=nq;
while go[p,c]=q do
begin
go[p,c]:=nq;
p:=fa[p];
end;
end;
end;
end; procedure change(c:char);
var p,np,q:longint;
begin
p:=go[last,c];
if mx[p]=mx[last]+ then last:=p
else begin
inc(t); np:=t;
mx[np]:=mx[last]+;
go[np]:=go[p];
fa[np]:=fa[p];
fa[p]:=np;
q:=last;
while go[q,c]=p do
begin
go[q,c]:=np;
q:=fa[q];
end;
last:=np;
end;
end; procedure dfs(x:longint);
var i:longint;
begin
inc(ti);
sa[ti]:=x; //dfs序上对应哪个点
b[x]:=ti;
i:=p[x];
while i<> do
begin
dfs(e[i].po);
i:=e[i].next;
end;
r[x]:=ti;
end; procedure dfss(x:longint);
var i:longint;
begin
g[x]:=g[x]+g[fa[x]];
i:=p[x];
while i<> do
begin
dfss(e[i].po);
i:=e[i].next;
end;
end; procedure work(x,w:longint);
begin
while x<=t do
begin
inc(c[x],w);
x:=x+lowbit(x);
end;
end; function ask(x:longint):longint;
begin
ask:=;
while x> do
begin
ask:=ask+c[x];
x:=x-lowbit(x);
end;
end; procedure put(x,y:longint);
begin
inc(len); //这个串x所有后缀所在的节点
w[len].po:=y;
w[len].next:=q[x];
q[x]:=len;
pr[len].po:=x; //节点代表了哪些串的后缀
pr[len].next:=h[y];
h[y]:=len;
end; function cmp(a,b:longint):boolean;
begin
exit(r[a]<r[b]);
end; procedure sort(l,r:longint);
var i,j,x:longint;
begin
i:=l;
j:=r;
x:=a[(l+r) shr ];
repeat
while cmp(a[i],x) do inc(i);
while cmp(x,a[j]) do dec(j);
if not(i>j) then
begin
swap(a[i],a[j]);
inc(i);
dec(j);
end;
until i>j;
if l<j then sort(l,j);
if i<r then sort(i,r);
end; begin
readln(n,k);
last:=; t:=;
for i:= to n do
begin
readln(s);
l:=length(s); last:=;
for j:=l downto do
begin
if go[last,s[j]]<> then change(s[j]) //广义后缀树
else add(s[j],i);
put(i,last);
end;
end;
len:=;
for i:= to t do
ins(fa[i],i); dfs();
for i:= to t do
a[i]:=i;
sort(,t);
j:=; x:=a[];
for i:= to t do
begin
f:=h[sa[i]];
while f<> do //因为一个节点可能代表了多个穿,插入相对麻烦
begin
y:=pr[f].po;
if cur[y]<> then work(cur[y],-);
cur[y]:=i;
work(i,);
f:=pr[f].next;
end;
while (j<=t) and (r[x]=i) do
begin
len:=ask(i)-ask(b[x]-);
if len<k then g[x]:= else g[x]:=mx[x]-mx[fa[x]];
inc(j); x:=a[j];
end;
if j=t+ then break;
end;
dfss();
for i:= to n do
begin
j:=q[i];
while j<> do
begin
x:=w[j].po;
ans[i]:=ans[i]+g[x];
j:=w[j].next;
end;
end;
for i:= to n do
write(ans[i],' ');
writeln;
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])

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

 var go:array[..*,''..''] of longint;
q,v,f:array[..] of longint;
fa,mx:array[..*] of longint;
ans,mid,i,n,m,last,t,l,r,j:longint;
s:ansistring;
c:char; function max(a,b:longint):longint;
begin
if a>b then exit(a) else exit(b);
end; procedure change(c:char);
var q,p,np:longint;
begin
p:=go[last,c];
if mx[p]=mx[last]+ then last:=p
else begin
inc(t); np:=t;
mx[np]:=mx[last]+;
go[np]:=go[p];
fa[np]:=fa[p];
fa[p]:=np;
q:=last;
while go[q,c]=p do
begin
go[q,c]:=np;
q:=fa[q];
end;
last:=np;
end;
end; procedure add(c:char);
var p,q,np,nq:longint;
begin
p:=last;
inc(t); last:=t; np:=t;
mx[np]:=mx[p]+;
while (p<>) and (go[p,c]=) do
begin
go[p,c]:=np;
p:=fa[p];
end;
if p= then fa[np]:=
else begin
q:=go[p,c];
if mx[q]=mx[p]+ then fa[np]:=q
else begin
inc(t); nq:=t;
mx[nq]:=mx[p]+;
go[nq]:=go[q];
fa[nq]:=fa[q];
fa[q]:=nq; fa[np]:=nq;
while go[p,c]=q do
begin
go[p,c]:=nq;
p:=fa[p];
end;
end;
end;
end; procedure match;
var i,j,l,t:longint;
begin
j:=; t:=;
l:=length(s);
for i:= to l do
begin
if go[j,s[i]]<> then
begin
inc(t);
j:=go[j,s[i]];
end
else begin
while (j<>) and (go[j,s[i]]=) do j:=fa[j];
if j= then
begin
t:=;
j:=;
end
else begin
t:=mx[j]+;
j:=go[j,s[i]];
end;
end;
v[i]:=t;
end;
end; function cmp(i,j:longint):boolean;
begin
exit(f[i]-i<f[j]-j);
end; function check(l0:longint):boolean;
var h,t,i,n:longint;
begin
n:=length(s);
for i:= to l0- do
f[i]:=;
h:=; t:=;
for i:=l0 to n do
begin
while (h<=t) and (cmp(q[t],i-l0)) do dec(t);
inc(t);
q[t]:=i-l0;
f[i]:=f[i-];
while (h<=t) and (q[h]<i-v[i]) do inc(h);
if h<=t then f[i]:=max(f[i],f[q[h]]+i-q[h]);
end;
if f[n]/n>=0.89999999999 then exit(true) else exit(false);
end; begin
readln(n,m);
t:=;
for i:= to m do
begin
readln(s);
last:=;
l:=length(s);
for j:= to l do
if go[last,s[j]]<> then change(s[j])
else add(s[j]);
end;
for i:= to n do
begin
readln(s);
match;
l:=;
r:=length(s);
while l<=r do
begin
mid:=(l+r) shr ;
if check(mid) then
begin
ans:=mid;
l:=mid+;
end
else r:=mid-;
end;
writeln(ans);
end;
end.

2806

UPD:以前写的广义后缀树有点冗长,最近转c++重新写了一份感觉好多了……

以我之前写的2780为例

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<vector> using namespace std;
vector<int> b[],q[];
//b[]记录每个节点是哪些串的子串,q[]记录每个串所有后缀所在的节点
struct way{int po,next;} e[];
struct node{int w,id;} a[];
int ans[],w[],go[][],fa[],mx[],l[],r[],p[],c[];
int len,t,last,n,m;
char s[]; bool cmp(node a,node b)
{
return l[a.w]<l[b.w];
} void work(int c) //比较优美的写法
{
int np,nq,q,p=last;
if (!go[last][c])
{
np=++t;
mx[np]=mx[p]+;
for (;p&&!go[p][c];p=fa[p]) go[p][c]=np;
}
else np=;
if (!p) fa[np]=;
else {
q=go[p][c];
if (mx[q]==mx[p]+) fa[np]=q;
else {
nq=++t;
mx[nq]=mx[p]+;
memcpy(go[nq],go[q],sizeof(go[q]));
fa[nq]=fa[q]; fa[q]=fa[np]=nq;
for (;go[p][c]==q;p=fa[p]) go[p][c]=nq;
}
}
last=go[last][c];
} void build(int x,int y)
{
e[++len].po=y;
e[len].next=p[x];
p[x]=len;
} void dfs(int x)
{
l[x]=++t; w[t]=x;
for (int i=p[x];i;i=e[i].next)
{
dfs(e[i].po);
}
r[x]=t;
} void add(int x,int w)
{
for (int i=x;i<=t;i+=i&-i) c[i]+=w;
} int ask(int x)
{
int s=;
for (int i=x;i;i-=i&-i) s+=c[i];
return s;
}
int main()
{
scanf("%d%d",&n,&m);
t=last=;
for (int i=; i<=n; i++)
{
scanf("%s",s+); len=strlen(s+);
last=;
for (int j=; j<=len;j++)
{
work(s[j]-'a');
b[last].push_back(i);
}
}
len=fa[]=;
for (int i=; i<=t; i++) build(fa[i],i);
t=; dfs();
for (int i=; i<=m; i++)
{
scanf("%s",s+); len=strlen(s+);
int j=;
for (int k=;k<=len;k++)
{
if (!go[j][s[k]-'a']) {j=;break;}
j=go[j][s[k]-'a'];
}
a[i].w=j; a[i].id=i;
}
sort(a+,a++m,cmp);
vector<int>::iterator k;
for (int i=;i<=t;i++)
for (k=b[w[i]].begin();k!=b[w[i]].end(); k++) q[*k].push_back(i);
vector<int>::iterator cur[];
for (int i=; i<=n; i++)
{
cur[i]=q[i].begin();
if (cur[i]!=q[i].end()) {add(*cur[i],); cur[i]++;}
}
int j=;
while (!a[j].w) j++;
for (int i=; i<=t; i++)
{
for (;l[a[j].w]==i; j++) ans[a[j].id]=ask(r[a[j].w])-ask(l[a[j].w]-);
for (k=b[w[i]].begin();k!=b[w[i]].end(); k++)
{
int x=*k;
if (cur[x]!=q[x].end()) {add(*cur[x],); cur[x]++;}
}
}
for (int i=; i<=m; i++) printf("%d\n",ans[i]);
system("pause");
return ;
}

2780(c++更新版)

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

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

    导言 最近软件安全课上,讲病毒特征码的提取时,老师讲了一下GST算法.这里就做个小总结. 简介 基本信息  广义后缀树的英文为Generalized Suffix Tree,简称GST. 算法目的   ...

  2. 字典树(trie树) 后缀树 广义后缀树

    转自:http://www.cnblogs.com/dong008259/archive/2011/11/11/2244900.html (1)字典树(Trie树) Trie是个简单但实用的数据结构, ...

  3. BZOJ 3926: [Zjoi2015]诸神眷顾的幻想乡(广义后缀自动机 多串)

    因为任何一条路径都可以看做某两个叶子节点之间路径的一部分,然后分别把20个叶节点当作根,把整棵树看作trie树,那么一条路径就能看作是从根到某个点这一条路的后缀,构建SAM就能维护不同子串的个数了. ...

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

    题目大意 给你一颗trie树,令\(s_i\)为点\(i\)到根的路径上的字符组成的字符串.求\(max_{u\neq v}(LCP(s_u,s_v)+LCS(s_u,s_v))\) \(LCP=\) ...

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

    题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r ...

  6. 从Trie树(字典树)谈到后缀树

    转:http://blog.csdn.net/v_july_v/article/details/6897097 引言 常关注本blog的读者朋友想必看过此篇文章:从B树.B+树.B*树谈到R 树,这次 ...

  7. [算法]从Trie树(字典树)谈到后缀树

    我是好文章的搬运工,原文来自博客园,博主July_,地址:http://www.cnblogs.com/v-July-v/archive/2011/10/22/2316412.html 从Trie树( ...

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

    (1)字典树(Trie树) Trie是个简单但实用的数据结构,通常用于实现字典查询.我们做即时响应用户输入的AJAX搜索框时,就是Trie开始.本质上,Trie是一颗存储多个字符串的树.相邻节点间的边 ...

  9. 后缀树(Suffix Tree)

          问题描述:               后缀树(Suffix Tree)   参考资料: http://www.cppblog.com/yuyang7/archive/2009/03/29 ...

随机推荐

  1. angularJs--$on、$emit和$broadcast的使用

    $emit只能向parent controller传递event与data $broadcast只能向child controller传递event与data $on用于接收event与data 例子 ...

  2. JavaScript笔记(二)——常用数组、字符串方法的应用

    1.将字符串中的字符翻转,比如'hello',翻转成'olleh'. var arr=[]; function reverseString(str) { arr=str.split("&qu ...

  3. php获取数组中重复数据的两种方法

    分享下php获取数组中重复数据的两种方法. 1,利用php提供的函数,array_unique和array_diff_assoc来实现 <?php function FetchRepeatMem ...

  4. 概念:RPG与RPGLE的区别

    RPG是OPM编程模式,即RPG编程的代码不能编译成*MODULE:编译只能直接生成一个程序,*PGM.    RPGLE是ILE编程模式.OS/400环境下,ILE是集成开发环境.在ILE环境下,所 ...

  5. http://phantomjs.org/page-automation.html

    http://phantomjs.org/page-automation.html install brew curl -LsSf http://github.com/mxcl/homebrew/ta ...

  6. java实现附件预览(openoffice+swfTools+FlexPaper) (转载)

    下边例子是在网上找了一个网友做的例子,在次记录 1.概述 主要原理 1.通过第三方工具openoffice,将word.excel.ppt.txt等文件转换为pdf文件 2.通过swfTools将pd ...

  7. jQuery uploadify-v3.1 批量上传

    引用: <link href="/UI.Web.CRM.Main/jQuery.Uploadify/uploadify.css" rel="stylesheet&q ...

  8. linux安装软件命令

    tar.bz2的解压: tar -jxvf FileName.tar.bz2 然后安装: cd FileName ./configure make make install rpm 包的安装: rpm ...

  9. 2017年iOS应用将强制使用HTTPS安全加密-b

    6月14日,WWDC 2016苹果开发者大会上,苹果在讲解全新的iOS10中提到了数据安全这一方面,并且苹果宣布iOS应用将从2017年1月起启用名为App Transport Security的安全 ...

  10. TWaver3D入门探索——3D拓扑图之人在江湖

    俗话说,有人的地方就有江湖,江湖就是帮派林立错综复杂的关系网.今天我们就来展示这样一个小小的江湖. 故事背景 崇祯末年,民不聊生,烽烟四起-- 江湖都是有背景的,我们的3D江湖也需要一个背景.江湖就是 ...