[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", 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

 

posted @ 2018-12-04 22:06  ww3113306  阅读(156)  评论(0编辑  收藏  举报
知识共享许可协议
本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。