码迷,mamicode.com
首页 > 其他好文 > 详细

后缀自动机习题合集

时间:2015-02-20 16:20:41      阅读:156      评论:0      收藏:0      [点我收藏+]

标签:

(写的都是初中小朋友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.
View Code

 

SPOJ-1811 Longest Common Substring

题意:求两个字符串的最长公共字串。

(似乎做sa的时候做过?然后翻出sa的代码,交上去光荣tle……sam虐杀sa!!)

继续贴丽洁姐论文话:

给两个长度小于100000的字符串A和B,求出他们的最长公共连续子串。
我们构造出A的后缀自动机SAM
对于SAM的每个状态s,令r为Right(s)中任意的一个元素,它代表的是结束位置在r的,长度在[Min(s),Max(s)]之间的所有子串。
我们不妨对状态s,求出所有B的子串中,从任意r开始往前能匹配的最大长度L,那么min(L,Max(s))就可以更新答案了。
 我们考虑用SAM读入字符串B
令当前状态为s,同时最大匹配长度为len
我们读入字符x。如果s有标号为x的边,
那么s=trans(s,x),len = len+1
否则我们找到s的第一个祖先a,它有标号为x的边,令
s=trans(a,x),len=Max(a)+1
如果没有这样的祖先,那么令s=root,len=0
在过程中更新状态的最大匹配长度(在这道题中我没有理解这句话,直接像以前处理kmp一样边跑边更新,后来写多个串的最长公共子串时就因为这个wa了好久!)
注意到我们求的是对于任意一个Right集合中的r,最大的匹配长度。那么对于一个状态s,它的结果自然也可以作为它Parent的结果,我们可以从底到上更新一遍。
然后问题就解决了。
技术分享
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.
View Code

 

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.
View Code

 

 

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.
View Code

 

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.
View Code

 

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.
View Code

(当然也可以快排询问,我懒所以没有做)

 

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.
View Code

 

待填之坑

SPOJ-8747. Substrings II && BZOJ-2555: SubString ( Suffix Automaton + Link Cut Tree )/BZOJ 2555 Substring (要lct吓傻)

BZOJ 3238 AHOI2013 差异 后缀自动机

BZOJ 2882 工艺 后缀自动机

后缀自动机习题合集

标签:

原文地址:http://www.cnblogs.com/Macaulish/p/4296557.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!