后缀自动机习题合集
(写的都是初中小朋友czl早就切过的题……)
http://www.cnblogs.com/Lyush/p/3281546.html
POJ-1509 Glass Beads
UVA - 719 Glass Beads
题意:一个字符串可以将第一个字符放到最后一位,然后问不断这样做可以得到的字典序最小的字符串
sam模板题,copy一遍建个sam,然后直接在sam中跑一遍就行了。
sam记录了字符串的所有后缀(也随便记录了字串),从root开始到每个接受态节点都是一个后缀(或多个),从root开始到每个节点都是一个子串(或多个)。由于copy一遍后把所有的情况的包括进去了,那么只要跑len(原长)遍就行了!
const maxn=30000; var step,pre:array[0..maxn]of longint; son:array[0..maxn,0..25]of longint; tot,tail,t:longint; procedure add(x:longint); var i,j,k,np,p:longint; begin p:=tail; inc(tot); np:=tot; step[np]:=step[tail]+1; while (p>=0) and (son[p,x]=-1) do begin son[p,x]:=np; p:=pre[p]; end; tail:=np; if p<0 then pre[np]:=0 else if step[son[p,x]]=step[p]+1 then pre[np]:=son[p,x] else begin j:=son[p,x]; inc(tot); k:=tot; for i:=0 to 25 do son[k,i]:=son[j,i]; step[k]:=step[p]+1; pre[k]:=pre[j]; pre[j]:=k; pre[np]:=k; while (p>=0) and (son[p,x]=j) do begin son[p,x]:=k; p:=pre[p]; end; end; end; procedure work; var s:ansistring; len,i,j,k,p:longint; begin readln(s); len:=length(s); s:=s+s; for i:=1 to len<<1 do add(ord(s[i])-97); p:=0; for i:=1 to len do for j:=0 to 25 do if son[p,j]>=0 then begin p:=son[p,j]; break; end; writeln(step[p]-len+1); end; begin readln(t); while t>0 do begin dec(t); fillchar(pre,sizeof(pre),255); fillchar(son,sizeof(son),255); fillchar(step,sizeof(step),0); tot:=0; tail:=0; work; end end.
SPOJ-1811 Longest Common Substring
题意:求两个字符串的最长公共字串。
(似乎做sa的时候做过?然后翻出sa的代码,交上去光荣tle……sam虐杀sa!!)
继续贴丽洁姐论文话:
那么s=trans(s,x),len = len+1
s=trans(a,x),len=Max(a)+1。
const maxn=400000; var son:array[0..maxn,0..25]of longint; pre,step:array[0..maxn]of longint; last,tot:longint; function addpoint:longint; var i:longint; begin inc(tot); //pre[tot]:=-1; // for i:=0 to 25 do son[tot,i]:=0; exit(tot); end; procedure add(x:longint); var i,new,now,old,more:longint; begin new:=addpoint; now:=last; step[new]:=step[now]+1; while (now>=0) and (son[now,x]=0) do begin son[now,x]:=new; now:=pre[now]; end; last:=new; if now<0 then pre[new]:=0 else if step[son[now,x]]=step[now]+1 then pre[new]:=son[now,x] else begin old:=son[now,x]; more:=addpoint; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; pre[more]:=pre[old]; pre[old]:=more; pre[new]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end; procedure work; var s:ansistring; now,max,long,i,j:longint; begin fillchar(pre,sizeof(pre),255); readln(s); for i:=1 to length(s) do add(ord(s[i])-97); readln(s); now:=0; max:=0; long:=0; for i:=1 to length(s) do begin j:=ord(s[i])-97; if son[now,j]<>0 then begin inc(long); now:=son[now,j]; end else begin while (now>=0) and (son[now,j]=0) do now:=pre[now]; if now<0 then begin long:=0; now:=0; end else begin long:=step[now]+1; now:=son[now,j]; end; end; if long>max then max:=long; end; writeln(max); end; begin work; end.
SPOJ-1812 Longest Common Substring II
题意:求多个字符串的最长公共字串。
还是以第一个字符串建个sam。然后其他字符串像上一道一样跑sam。
那么状态s,其他串对它的匹配长度分别是a1,a2,a3……an的话,状态s的最长公共字符串就是min(a1,a2,a3……an,max(s))。
然后去最长的状态就行了!
const maxn=200033; var p,num,step,ans,pre:array[0..maxn]of longint; son:array[0..maxn,0..25]of longint; tot,last:longint; function max(x,y:longint):longint; begin if x<y then exit(y); exit(x); end; function min(x,y:longint):longint; begin if x<y then exit(x); exit(y); end; procedure add(x:longint); var i,now,new,more,old:longint; begin inc(tot); new:=tot; now:=last; last:=new; step[new]:=step[now]+1; while (now>=0) and (son[now,x]=0) do begin son[now,x]:=new; now:=pre[now]; end; if now<0 then pre[new]:=0 else begin old:=son[now,x]; if step[now]+1=step[old] then pre[new]:=old else begin inc(tot); more:=tot; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; pre[more]:=pre[old]; pre[old]:=more; pre[new]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end; end; procedure into; var s:ansistring; i,len:longint; begin tot:=0; pre[0]:=-1; readln(s); len:=length(s); for i:=1 to len do add(ord(s[i])-97); fillchar(num,sizeof(num),0); for i:=1 to tot do inc(num[step[i]]); for i:=1 to len do inc(num[i],num[i-1]); for i:=1 to tot do begin p[num[step[i]]]:=i; dec(num[step[i]]); end; for i:=1 to tot do begin num[i]:=0; ans[i]:=step[p[i]]; end; end; procedure work; var s:ansistring; i,j,now,long,x,answer,n:longint; begin while not eof do begin readln(s); now:=0; long:=0; for i:=1 to length(s) do begin j:=ord(s[i])-97; if son[now,j]<>0 then begin now:=son[now,j]; inc(long); num[now]:=max(num[now],long); end else begin while (now>=0) and (son[now,j]=0) do now:=pre[now]; if now<0 then begin now:=0; long:=0; end else begin long:=step[now]+1; now:=son[now,j]; num[now]:=max(num[now],long); end; end; end; for i:=tot downto 1 do begin x:=p[i]; step[x]:=min(step[x],num[x]); num[pre[x]]:=max(num[pre[x]],num[x]); num[x]:=0; end; end; answer:=0; for i:=1 to tot do if answer<step[i] then answer:=step[i]; writeln(answer); end; begin into; work; end.
SPOJ-8222 Substrings
题意:给一个字符串s,求长度为i(i属于【1,len】)的出现次数最多的子串出现的次数(好绕)……
上一篇有提到子串出现的个数就是right集合的大小。
那么用那个方法就行了(其实不用dfs序,可以用拓扑和桶排,拓扑要开多几个数组,桶排不用……速度没比过)
注意right树中要用儿子更新父亲,就是长度为i+1可以更新长度为i的值……
type arr=record from,next:longint; end; const maxn=600000; var edge:array[0..maxn]of arr; ans,step,first,sum,num,pre,p:array[0..maxn]of longint; son:array[0..maxn,0..25]of longint; tot,last,len,esum:longint; function max(x,y:longint):longint; begin if x>y then exit(x); exit(y); end; function addpoint:longint; begin inc(tot); exit(tot); end; procedure addedge(j,k:longint); begin inc(esum); edge[esum].from:=j; edge[esum].next:=first[k]; first[k]:=esum; inc(num[j]); end; procedure add(x:longint); var now,new,more,old,i:longint; begin new:=addpoint; now:=last; step[new]:=step[now]+1; while (now>=0) and (son[now,x]=0) do begin son[now,x]:=new; now:=pre[now]; end; last:=new; if now<0 then pre[now]:=0 else begin old:=son[now,x]; if step[old]=step[now]+1 then pre[new]:=old else begin more:=addpoint; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; pre[more]:=pre[old]; pre[new]:=more; pre[old]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end; end; procedure into; var s:ansistring; i,j:longint; begin readln(s); pre[0]:=-1; last:=0; len:=length(s); for i:=1 to len do add(ord(s[i])-97); for i:=1 to tot do for j:=0 to 25 do if son[i,j]<>0 then addedge(i,son[i,j]); end; procedure work; var head,tail,i,x,fa:longint; begin head:=1; tail:=0; while last>=0 do begin sum[last]:=1; last:=pre[last]; end; for i:=0 to tot do if num[i]=0 then begin inc(tail); p[tail]:=i; end; while head<=tail do begin x:=p[head]; i:=first[x]; while i>0 do begin fa:=edge[i].from; inc(sum[fa],sum[x]); dec(num[fa]); if num[fa]=0 then begin inc(tail); p[tail]:=fa; end; i:=edge[i].next; end; inc(head); end; for i:=1 to tot do ans[step[i]]:=max(ans[step[i]],sum[i]); for i:=len-1 downto 1 do ans[i]:=max(ans[i],ans[i+1]); for i:=1 to len do writeln(ans[i]); end; begin into; work; end.
SPOJ-7258 Lexicographical Substring Search
题意:给定一个字符串,取出所有的子串按照字典序排序并去重后,求第K大的子串。
先预处理出每个节点的所包含子串总数,然后对每个询问在sam上爬一爬就行了!
由于spoj卡得紧,处理询问的时候不能从0-25都扫一遍,要先记录儿子的数量,具体看代码吧。
const maxn=200000; var step,num,sum,pre,p:array[0..maxn]of longint; col,too,son:array[0..maxn,0..25]of longint; last,tot:longint; procedure add(x:longint); var i,new,now,more,old:longint; begin inc(tot); new:=tot; now:=last; step[new]:=step[now]+1; last:=new; while (now>=0) and (son[now,x]=0)do begin son[now,x]:=new; now:=pre[now]; end; if now<0 then pre[new]:=0 else begin old:=son[now,x]; if step[old]=step[now]+1 then pre[new]:=old else begin inc(tot); more:=tot; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; pre[more]:=pre[old]; pre[new]:=more; pre[old]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end; end; procedure into; var s:ansistring; i,j,x,len:longint; begin pre[0]:=-1; last:=0; tot:=0; readln(s); len:=length(s); for i:=1 to len do add(ord(s[i])-97); fillchar(num,sizeof(num),0); for i:=1 to tot do begin sum[i]:=1; inc(num[step[i]]); end; for i:=2 to len do inc(num[i],num[i-1]); for i:=1 to tot do begin p[num[step[i]]]:=i; dec(num[step[i]]); end; fillchar(num,sizeof(num),0); for i:=tot downto 0 do begin x:=p[i]; for j:=0 to 25 do if son[x,j]<>0 then begin inc(num[x]); col[x,num[x]]:=j; too[x,num[x]]:=son[x,j]; inc(sum[x],sum[son[x,j]]); end; end; //for i:=0 to tot do writeln(sum[i]); end; procedure work; var q,n,i,k,now:longint; answer:ansistring; begin readln(q); while q>0 do begin dec(q); readln(n); answer:=''; now:=0; while n>0 do begin for i:=1 to num[now] do begin k:=too[now,i]; if sum[k]<n then dec(n,sum[k]) else begin dec(n); answer:=answer+chr(97+col[now,i]); now:=k; break; end; end; end; writeln(answer); end; end; begin into; work end.
HDU-4622 Reincarnation
题意:给定一个字符串,多组询问,每个询问给一个区间,求出该区间内共有多少个不同的子串。
如果是单个子串好处理&(sa和sam都可以嘛)。
但是现在要求区间。我们可以这样考虑,每次按左端点以此往右端点建。比如当前是【l,r】然后变成【l,r+1】的话,就是sam(【l,r】)变成sam(【l,r+1】)。多出的子串就是step【last】-step【pre【last】】(这个很重要,在统计时经常用到这个式子)
然后就直接处理出所有区间就行了……(暴力到不敢相信)
const maxn=5000; var ans:array[0..2100,0..2100]of longint; pre,step:array[0..maxn]of longint; son:array[0..maxn,0..25]of longint; tot,last,t,n,len,i,j:longint; s:ansistring; function addpoint:longint; begin inc(tot); pre[tot]:=-1; fillchar(son[tot],sizeof(son[tot]),0); addpoint:=tot end; procedure add(x:longint); var i,now,new,old,more:longint; begin new:=addpoint; now:=last; last:=new; step[new]:=step[now]+1; while (now>=0) and (son[now,x]=0) do begin son[now,x]:=new; now:=pre[now]; end; if now<0 then pre[new]:=0 else begin old:=son[now,x]; if step[old]=step[now]+1 then pre[new]:=old else begin more:=addpoint; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; pre[more]:=pre[old]; pre[old]:=more; pre[new]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end end; begin readln(t); while t>0 do begin dec(t); readln(s); len:=length(s); for i:=1 to len do begin tot:=-1; last:=addpoint; ans[i,i-1]:=0; for j:=i to len do begin add(ord(s[j])-97); ans[i,j]:=ans[i,j-1]+step[last]-step[pre[last]]; end; end; readln(n); while n>0 do begin dec(n); readln(i,j); writeln(ans[i,j]); end; end end.
(当然也可以快排询问,我懒所以没有做)
HDU-4641 K-string
题意:给定字符串,有m次操作,每次操作可以向该字符串末尾添加一个字符或者询问在字符串中出现了至少K次的子串一共有多少个?
就是在插入字符后顺便统计一下就行了,如果这个点出现次数大于k那么一定已经在ans中了。
要注意的是在step【old】>step【now】+1时复制点时,出现次数要也一起复制过去……
const maxn=500000; var step,num,pre:array[0..maxn]of longint; son:array[0..maxn,0..25]of longint; ans,tot,last,n,m,kk,i,j:longint; ch:char; s:ansistring; function addpoint:longint; var i:longint; begin inc(tot); pre[tot]:=-1; step[tot]:=0; num[tot]:=0; for i:=0 to 25 do son[tot,i]:=0; addpoint:=tot; end; procedure add(x:longint); var i,now,new,more,old:longint; begin new:=addpoint; now:=last; last:=new; step[new]:=step[now]+1; while (now>=0) and (son[now,x]=0) do begin son[now,x]:=new; now:=pre[now]; end; if now<0 then pre[new]:=0 else begin old:=son[now,x]; if step[old]=step[now]+1 then pre[new]:=old else begin more:=addpoint; for i:=0 to 25 do son[more,i]:=son[old,i]; step[more]:=step[now]+1; num[more]:=num[old]; pre[more]:=pre[old]; pre[old]:=more; pre[new]:=more; while (now>=0) and (son[now,x]=old) do begin son[now,x]:=more; now:=pre[now]; end; end; end; now:=last; while (now>0) and (num[now]<kk) do begin inc(num[now]); if num[now]=kk then ans:=ans+step[now]-step[pre[now]]; now:=pre[now]; end; end; begin while not eof do begin readln(n,m,kk); tot:=-1; last:=addpoint; ans:=0; readln(s); for i:=1 to length(s) do add(ord(s[i])-97); while m>0 do begin dec(m); read(j); if j=1 then begin read(ch); read(ch); add(ord(ch)-97); {for i:=0 to tot do begin write(i,':'); for j:=0 to 25 do write(son[i,j],' '); writeln; end; } end else writeln(ans); readln; end; end end.
待填之坑
SPOJ-8747. Substrings II && BZOJ-2555: SubString ( Suffix Automaton + Link Cut Tree )/BZOJ 2555 Substring (要lct吓傻)
BZOJ 3238 AHOI2013 差异 后缀自动机
BZOJ 2882 工艺 后缀自动机
2015.04.14……看看人家学习的深度……唉,还是太弱了 http://www.cnblogs.com/zyfzyf/p/4397216.html#3161819