zoukankan      html  css  js  c++  java
  • 后缀自动机习题合集

    (写的都是初中小朋友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 工艺 后缀自动机

    2015.04.14……看看人家学习的深度……唉,还是太弱了 http://www.cnblogs.com/zyfzyf/p/4397216.html#3161819

  • 相关阅读:
    Oracle配置监听
    Oracle创建表空间和分配用户权限
    Dijkstra
    【刷题】【dp】【记忆化搜索】单词游戏
    【刷题】【记忆化搜索】【dp】Longtail Hedgehog
    【刷题】【dp】 Make The Fence Great Again
    【技巧】【卡常】
    【二分】【基础】(跳石头)(合并果子)(蚯蚓)
    【笔记】两种交换元素的方案对比
    【刷题】【单调栈】请客
  • 原文地址:https://www.cnblogs.com/Macaulish/p/4296557.html
Copyright © 2011-2022 走看看