SPOJ - NSUBSTR Substrings(后缀自动机)

题目传送门


解题思路:

题意是求一个串各个长度的子串的可重叠最大重复出现次数。

首先我们肯定能想到构造后缀自动机的时候每加入一个新的字符新产生的子串长度一定是跟当前节点有关。用cnt[]记录每个节点的串出现的次数,在一个个添加字符的时候,每次在这个新产生的后缀节点+1,我们知道如果这个节点没有对应符合要求的父节点的话还会再产生一个新的节点,那个节点不用记,因为那个节点也属于当前节点的后缀,我们等会儿从最底部叶子节点更新,这个点的值会被更新对,如果现在+1等会儿会重复。

于是我们把整个串扔进后缀自动机之后,就把每次添加字符对应的节点都+1,那么怎么正确求得cnt[]呢?

由于sam(后缀自动机)中的节点不像pam(回文自动机)那样所有的儿子下标都比自己大,pam可以直接从下标大的更新,sam不可以。

但是本质还是按照先更新儿子,再更新父亲的顺序。

我们把所有节点按照节点字符串长度从大到小更新,儿子一定比父亲长,就保证了之前所说。很明显不能排序,排序直接超时,可以开个|S|个vector暴力存。

然后求得cnt[]之后,用类似差分的方法,sam上每个节点都有一个len记录长度,我们先用len更新答案ans[],最后倒着更新一遍即可。

意思就是:

_ 4 _ 3 _ _ 2 _ 1(len更新的答案)

倒着再更新一遍后:443322211

 

代码:

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pt(x) printf("%s = %d\n",#x,x)
#define INF 0x3f3f3f3f
#define pb push_back

const int N = 250000+5;

char s[N];

const int maxn = 250000+5;
const int ALP = 26;

struct SAM
{
    int trie[maxn<<1][ALP];
    int fa[maxn<<1];
    int len[maxn<<1];
    int sz,last;
    int cnt[maxn<<1];
    int cf[maxn];//记录答案,差分取首字母...
    vector<int>v[maxn];

    void init(){
        sz = last = 0;
        newnode();
        fa[0] = -1,len[0] = 0;
        memset(cnt,0,sizeof cnt);
    }

    int newnode(){
        memset(trie[sz],0,sizeof trie[sz]);
        if (sz<=250000) v[sz].clear();//节点的下标<=长度,因此随用随初始化即可
        return sz++;
    }

    int idx(char ch){
        return ch-'a';
    }

    void add(char ch){
        int c = idx(ch);
        int p = last,np = newnode();
        last = np;
        len[np] = len[p]+1;
        cnt[np]++;//只加np,而nq不加。因为nq是自己创建的辅助节点
        v[len[np]].pb(np);
        for (;~p && !trie[p][c];p = fa[p]) trie[p][c] = np;
        if (p==-1) fa[np] = 0;
        else {
            int q = trie[p][c];
            if (len[q]==len[p]+1) fa[np] = q;
            else {
                int nq = newnode();
                fa[nq] = fa[q];
                len[nq] = len[p]+1;
                for (int i=0;i<ALP;i++) trie[nq][i] = trie[q][i];
                fa[np] = fa[q] = nq;
                for (;~p && trie[p][c]==q;p = fa[p]) trie[p][c] = nq; 
                v[len[nq]].pb(nq);
            }
        }
    }

    void count(int Len){
        for (int i=1;i<=Len;i++) cf[i] = 1;
        for (int i=Len;i>=1;i--)
            for (int j=0;j<v[i].size();j++) cnt[fa[v[i][j]]] += cnt[v[i][j]];
        for (int i=1;i<sz;i++) cf[len[i]] = max(cf[len[i]],cnt[i]);
        for (int i=Len-1;i>=1;i--) cf[i] = max(cf[i],cf[i+1]);
        for (int i=1;i<=Len;i++) printf("%d\n",cf[i]);  
    }
}sam;

int main()
{
    while (~scanf("%s",s)){
        sam.init();
        int len = strlen(s);
        for (int i=0;i<len;i++) sam.add(s[i]);
        sam.count(len);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值