【后缀自动机】SPOJ 8222-最多重复子串

题意:

  一个长度不超过250000的字符串,求出它长度为i的子串最多出现了多少次。

  

  后缀自动机练习题...虽说是用Cube评测的不过时限仍然鬼畜。

考虑SAM的性质:

  一个串的SAM肯定可以接受这个串的所有子串。SAM上的每一个点代表了一个子串。

  主链:SAM上最长的那条链,也就是说从根走到主链的尾端就是整个字符串。

  用t[i]记录i号点表示的子串的出现次数,沿着主链走一遍,很明显,主链上所有的点都代表了一个前缀,每个前缀都出现过。因此主链上的点的t初始值为1。

  接着,之前提到过,一个点的pre上的点和它,这些点表示的子串,具有相同的后缀。

  既然i出现过,那么i.pre肯定出现过。因此对一个点+1就代表对整条pre链+1。

  最后,因为step[i]表示i的长度,因此对于所有的step[i]取一个最大值,记录到ans中就可以了。

这次换了个SAM的写法,把之前的for循环改成了while,结果忘记了一个迭代步骤,调了统计过程很久才发现原来是SAM构错了...

一开始写的是t[i]+1之后,沿着pre链,将i的pre上的所有点都+1,结果TLE了。参考了一下沐阳神犇的代码,发现只要统计前缀和就行了...

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <iostream>
 4 #include <cstring>
 5 #define maxn 255000*2
 6 using namespace std;
 7
 8 struct node { int s[26],num,pre; } Suf[maxn];
 9 int step[maxn],ans[maxn],t[maxn],f[maxn];
10 int i,j,n,m,k,cnt = 1,last = 1,root = 1,len;
11 char S[maxn];
12
13 void add(int nxt)
14 {
15     int now = ++cnt, p = last;
16     step[now] = step[p] + 1;
17     while ( p && !Suf[p].s[nxt] ) Suf[p].s[nxt] = now, p = Suf[p].pre;
18     if (!p) Suf[now].pre = root;
19     else
20     {
21         int q = Suf[p].s[nxt];
22         if (step[q] == step[p] + 1) Suf[now].pre = q;
23         else
24         {
25             int nq = ++cnt;
26             step[nq] = step[p] + 1;
27             Suf[nq] = Suf[q];
28             Suf[q].pre = Suf[now].pre = nq;
29             while ( p && Suf[p].s[nxt] == q ) Suf[p].s[nxt] = nq,p = Suf[p].pre;
30         }
31     }
32     last = now;
33 }
34
35 bool cmp(int a,int b) { return step[a] < step[b]; }
36
37 int main()
38 {
39     len = strlen(S+1);
40
41     for (i = 1; i <= len; i++) add(S[i]-‘a‘);
42
43     for (i = 1; i <= cnt; i++) f[i] = i;
44     stable_sort(f+1,f+1+cnt,cmp);
45
46     int now = root;
47     for (i = 1; i <= len; i++)
48     {
49         now = Suf[now].s[S[i]-‘a‘];
50         t[now]++;
51     }
52
53     for (i = cnt; i > 1; i--)
54     {
55         ans[step[f[i]]] = max(ans[step[f[i]]],t[f[i]]);
56         if (Suf[f[i]].pre > 1) t[Suf[f[i]].pre] += t[f[i]];
57     }
58
59     for (i = 1; i <= len; i++)
60         printf("%d\n",ans[i]);
61
62     return 0;
63 }

SPOJ 8222

  

时间: 2024-08-04 13:54:12

【后缀自动机】SPOJ 8222-最多重复子串的相关文章

【BZOJ2320】最多重复子串 调和级数+hash

[BZOJ2320]最多重复子串 Description 一个字符串P的重复数定义为最大的整数R,使得P可以分为R段连续且相同的子串.比方说,“ababab”的重复数为3,“ababa”的重复数为1. Your Task 对于给定的串S,找出S的一个子串K使得K的重复数最大. Input 第一行T表示数据组数 对于每组数据,一行中一个仅包含小写字母的字符串S Output 对于每组数据,在一行中输出K,如果有多个解,输出字典序最小的那一个 Sample Input 2 ccabababc daa

SPOJ 687. Repeats(后缀数组求最长重复子串)

题目大意:给你一个串让你求出重复次数最多的连续重复子串的重复次数. 解题思路:论文上给出的解答是: 这还没完,因为经过这两个点的情况还不完备,应还可以假设起点在 [ i*j-i+1, i*j-d],其中 d = i-L/i (d = i-L%i)其意义为根据已知的匹配长度,可以将起点往前移动的范围,太靠后将不能够构造出比之前更好的解.如果要求出某个最多的连续重复子串的最小字典序子需要枚举所有起点,但如果只是要的到最多的重复次数或者任意最多的连续重复子串,那么只需要枚举i*j-d处的起点即可,因为

BZOJ2320 : 最多重复子串

本题就是求重复数最多的字典序最小的$runs$,如果重复数为1,那么做法显然,然后只考虑重复数大于1的情况. 从小到大枚举长度$len$,对于每个关键点$x=i\times len$,有且仅有一个长度为$len$的串经过它. 算出$x$与$x+len$的最长公共前缀$A$和最长公共后缀$B$后,贡献为$\lfloor\frac{A+B-1}{len}\rfloor+1$. 对于方案,可以暴力枚举所有可行起点,因为极大$runs$的总个数是$O(n)$的. 时间复杂度$O(n\log^2n)$.

POJ 3693 Maximum repetition substring(后缀数组求最长重复子串)

题目大意:和spoj687类似,就是当长度相同是需要输出一个最小的字典序的序列. 解体思路:这次需要枚举所有的从i到d = i-L/i (d = i-L%i)的位置,然后记录保证最大值的同时,求出来字典序最小的. Maximum repetition substring Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7418   Accepted: 2217 Description The repetition numb

SETI ACdream - 1430 后缀自动机求不相交子串

http://blog.csdn.net/gatevin/article/details/45875343 题目是求不重叠的不同子串个数 一般来说,endpos集合包含了子串结尾位置,每个状态都包含了若干子串.endpos集合的大小就是这些子串的出现次数 但是这样会重叠.那么可以求出endpos结合的结尾最小值,和结尾最大值. 那么长度小于mx - mi的子串,是肯定不会重叠的 至于有多少个,可以由mxcnt决定 #include <bits/stdc++.h> #define IOS ios

HIHOcoder1465 后缀自动机五&#183;重复旋律8

思路 后缀自动机求最长循环串 首先有一个常用的处理技巧,将串复制一遍,长度大于n的子串中就包含了一组循环子串 然后是后缀自动机如何处理最长公共子串的问题 维护两个变量,u和l,u代表当前位置的最长公共子串在哪个状态中,l代表当前位置的最长公共子串的长度 然后如果当前位置有向T[i+1]转移的路径,则转移,u=trans[u][T[i]],l=l+1 如果当前位置没有转移路径,则沿suflink回跳到有转移路径的状态,如果跳到初始状态仍然没有满足条件的节点,就变成初始条件即可 注意两个地方,第一个

E. Three strings 广义后缀自动机

http://codeforces.com/problemset/problem/452/E 多个主串的模型. 建立一个广义后缀自动机,可以dp出每个状态的endpos集合大小.同时也维护一个R[]表示那个串出现过. 所以可以算出每个状态的dp[i][k]表示第k个串在第i个状态中出现的次数. 可以知道sigma dp[i][0...k]是等于  endpos集合的大小. 然后把这个贡献加到min(i)....max(i)中去就可以了 差分一下. #include <bits/stdc++.h>

关于后缀自动机的一些理解

咕咕咕了好久的东西,以前只是粗糙的背个板子之类的,并未对parent树有什么深刻的理解 clj姐姐tql,这么神仙的东西是怎么想出来的啊 个人认为有以下几点是难以理解的: 一.right集合的定义: right集合就是指某一子串在原串当中出现的所有的位置的集合,也在某位大佬博客中叫做endpos集合: 二.right集合(endpos集合)相同的我们称为endpos等价类(重点理解) 三.对于任意两个endpos集合中,那么必然为其中一个为另一个的子串,或者两者绝无交集 四.endpos的等价类

【SPOJ】8222. Substrings(后缀自动机)

http://www.spoj.com/problems/NSUBSTR/ 题意:给一个字符串S,令F(x)表示S的所有长度为x的子串中,出现次数的最大值.求F(1)..F(Length(S)) 这题做法: 首先建立字符串的后缀自动机. 因为自动机中的每个状态都代表一类子串前缀,且任意状态的最长的|max|所对应的子串是唯一的. 所以我们算出每个子串(即找到的状态是end态),他们的right值为++(即保证长度为当前子串的出现次数为1),然后自底向上在parent树中更新right值(pare