【SPOJ - NSUBSTR】Substring(统计长度为x的子串最多出现的次数--后缀自动机)

题目地址:https://cn.vjudge.net/problem/SPOJ-NSUBSTR

解题思路:


建立SAM,先统计每个状态的最长子串出现的次数(应该是这个意思吧,这个状态substring中的每个子串也都出现了相同的次数),每个状态st的substring中的子串长度区间为[maxlen[link[st]]+1,maxlen[st]]。

对于状态st1,substring(st1)中的子串长度满足ans[i]=ans[i+1],当i=maxlen[st1]时,i+1之后子串就会跳转到另一个状态st2,而且substring(st1)的最长子串s1是substring(st2)最长子串s2的后缀,s1的出现次数不会比s2少,所以在更新答案是依旧满足ans[i] = max(ans[i], ans[i+1])。

我。。。说的好啰嗦。。。。(读者请仔细看完这篇博客前面的原理部分,可以参照下面的图理解)

【SPOJ - NSUBSTR】Substring(统计长度为x的子串最多出现的次数--后缀自动机)_第1张图片

总之对应的代码就是酱紫的:

for(int i = 1; i <= num; i++) ans[maxlen[i]] = max(ans[maxlen[i]], cnt[i]);
for(int i = len-1; i >= 1; i--) ans[i] = max(ans[i], ans[i+1]);

 

ac代码:


#include 
using namespace std;
typedef long long ll;
const ll maxn = 1e6+100;
int maxlen[maxn<<1], trans[maxn<<1][30], link[maxn<<1];
int x[maxn<<1], y[maxn<<1], cnt[maxn<<1];
int num, last;//num表示状态数
char s[maxn];
int ans[maxn]={0};
void init()
{
    num = last = 1;//起始状态的编号为1
    maxlen[last] = link[last] = 0;//起始位置为空字符,长度为0
}
void insert(int id)
{
    int z = ++num, p;
    maxlen[z] = maxlen[last]+1;
    for(p = last; p && !trans[p][id]; p = link[p]) trans[p][id] = z;
    if(!p) link[z] = 1;//路径上不存在跳转
    else//连接路径上已存在跳转
    {
        int x = trans[p][id];//第一个存在跳转的状态跳转去的状态
        if(maxlen[x] == maxlen[p]+1) link[z] = x;//一个
        else//多个
        {
            int y = ++num;
            maxlen[y] = maxlen[p]+1;
            memcpy(trans[y], trans[x], sizeof(trans[x]));
            link[y] = link[x];
            for(; p && trans[p][id] == x; p = link[p]) trans[p][id] = y;
            link[x] = link[z] = y;
        }
    }
    last = z;
    cnt[z] = 1;
}
void count()
{
    memset(x, 0, sizeof x);
    for(int i = 1; i <= num; i++) x[maxlen[i]]++;
    for(int i = 1; i <= num; i++) x[i] += x[i-1];
    for(int i = 1; i <= num; i++) y[x[maxlen[i]]--] = i;
    for(int i = num; i >= 1; i--)
        cnt[link[y[i]]] += cnt[y[i]];//得到每个状态的字符串集出现的次数
}
void getans(int len)//统计长度为x的子串最多出现的次数
{
    for(int i = 1; i <= num; i++) ans[maxlen[i]] = max(ans[maxlen[i]], cnt[i]);
    for(int i = len-1; i >= 1; i--) ans[i] = max(ans[i], ans[i+1]);
}
int main()
{
    scanf("%s", s);
    int len = strlen(s);
    init();
    for(int i = 0; i < len; i++) insert(s[i]-'a');
    count();
    getans(len);
    for(int i = 1; i <= len; i++)
        printf("%d\n", ans[i]);
    return 0;
}

 

你可能感兴趣的:(后缀自动机)