• [COCI2011-2012#5] POPLOCAVANJE 后缀自动机


    题面:洛谷

    题解:

      其实还可以用AC自动机做,但是没调出来,,,不知道发生了什么。。。

      AC自动机做法如下:

        观察到如果我们对给定的每个串建AC自动机,那么直接拿大串在上面匹配,如果遇到了一个单词的终止节点,假设当前大串的位置是i,匹配到的节点是j,那么这个单词覆盖了

        [i - dep[j] + 1, i]这个区间(dep[j]即为单词长度)

        但是我们发现空间根本开不下。

        因为对每个串分别匹配不会影响结果,所以考虑每50个串我们重建一次AC自动机,然后匹配一次。

        匹配中的每次区间修改都用差分维护,那么最后对于每个权值大于等于1的位置都统计1 的贡献即为答案。

      后缀自动机做法如下:

        考虑统计对于每个位置而言,以i为结尾的最长被覆盖的长度

        我们对大串建立后缀自动机,然后对于每个小串都拿去自动机上匹配。假设我们最后匹配到的状态为x,既然这个状态已经被覆盖了,那么x在parent树上的子树也一定可以被覆盖。

        但是要对每个状态内的最长串长度MAXS取min,因为匹配到一个状态只能代表可以覆盖这个状态所代表的子串,且长度不能大于当前给定图案(小串)的长度。

        那么我们对每次匹配到的状态x打上标记,标记权值为当前小串的长度,如果有多个标记,取最大的那个。

        最后统计打完所有标记之后,对于每个节点都下传标记,下传的过程中依然对标记权值取max。

        最后即可得到对于每个位置而言,以i为结尾的最长被覆盖的长度

        

        一个快速(好写)统计的方法:

          因为对于每个点下传标记就相当于对每个点接收来自它父亲的标记,那么我们只需要保证在接收点x的父亲的标记时,fa[x]已经接收过来自fa[fa[x]]的标记。

          因此我们按bfs序来更新标记就可以了,因为l[fa[x]](l[i]表示状态i的MAXS,即最长的保证right集合不变的长度)一定小于l[x],所以我们按照l[x]的大小来确定顺序即可。

      

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define R register int
     4 #define AC 301000
     5 #define ac 601000
     6 
     7 int n, m, ans;
     8 int b[AC], k[ac];
     9 int d[AC], mark[ac];//差分数组
    10 char s[AC];
    11 
    12 inline void upmax(int &a, int b){
    13     if(b > a) a = b;
    14 }
    15 
    16 struct sam_atm{
    17     int last, cnt;
    18     int ch[ac][26], fa[ac], l[ac], right[ac];
    19 
    20     inline void add(int c, int i)
    21     {
    22         int p = last, np = ++ cnt;
    23         last = np, l[np] = l[p] + 1, right[np] = i;
    24         for( ; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;
    25         if(!p) fa[np] = 1;
    26         else
    27         {
    28             int q = ch[p][c];//获取第一个有c的节点的对应边所指向的节点
    29             if(l[p] + 1 == l[q]) fa[np] = q;//如果不会造成影响,那么就直接连fa
    30             else
    31             {
    32                 int nq = ++ cnt;//否则就再建新点
    33                 l[nq] = l[p] + 1;
    34                 memcpy(ch[nq], ch[q], sizeof(ch[q]));//把q的信息赋值给nq,,,接下来nq相当于要取代q了
    35                 fa[nq] = fa[q], fa[q] = fa[np] = nq;
    36                 for( ; ch[p][c] == q; p = fa[p]) ch[p][c] = nq; 
    37             }
    38         }
    39     }
    40 
    41     void find()
    42     {
    43         int len = strlen(s + 1), now = 1;
    44         for(R i = 1; i <= len; i ++)
    45         {
    46             if(!ch[now][s[i] - 'a']) return ;
    47             now = ch[now][s[i] - 'a'];
    48         }
    49         upmax(mark[now], len);
    50     }
    51     
    52     void work()
    53     {
    54         for(R i = 1; i <= cnt; i ++) ++ b[l[i]];
    55         for(R i = 1; i <= n; i ++) b[i] += b[i - 1];//这里只需要枚举到n,多枚举就re了
    56         for(R i = 1; i <= cnt; i ++) k[b[l[i]] --] = i;//为i赋b[l[i]]的排名
    57         for(R i = 1; i <= cnt; i ++) upmax(mark[k[i]], mark[fa[k[i]]]);        
    58 
    59         for(R i = 1; i <= cnt; i ++) //只能用有right的叶子节点更新
    60             if(right[i]) ++ d[right[i] - mark[i] + 1], -- d[right[i] + 1];
    61         for(R i = 1; i <= n; i ++) d[i] += d[i - 1];
    62         for(R i = 1; i <= n; i ++) ans += (d[i] > 0);
    63         printf("%d
    ", n - ans);
    64     }
    65 }T;
    66 
    67 void pre()
    68 {
    69     T.last = T.cnt = 1;//这个又忘了。。。
    70     scanf("%d%s", &n, s + 1);
    71     for(R i = 1; i <= n; i ++) T.add(s[i] - 'a', i);
    72     scanf("%d", &m);
    73     for(R i = 1; i <= m; i ++) scanf("%s", s + 1), T.find();
    74 }
    75 
    76 int main()
    77 {
    78 //    freopen("in.in", "r", stdin);
    79     pre();
    80     T.work();
    81 //    fclose(stdin);
    82     return 0;
    83 }
    View Code
  • 相关阅读:
    C++之容器
    C++之复制控制
    TCP的3次握手/4次握手
    linux编程之多线程编程
    linux编程之信号
    linux编程之共享内存
    MySQL数据库优化
    MySQL存储引擎InnoDB与Myisam
    Redis--持久化
    Redis 请求应答模式和往返延时 Pipelining
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10067676.html
Copyright © 2020-2023  润新知