spoj 8222 Substrings(NSUBSTR),后缀自动机

spoj 8222  Substrings

f[i]指长度为i的串出现次数的最大值。这里的不同出现指,可以有重复串,只要起始位置不同就视为不同的出现。

求f[1]..f[lenth]。

 

怎么求可重复的一个串出现了多少次。

LCS那篇里提到了,通过fa指针回退,会回到一个状态,这个状态的接受串与当前状态的接受串后缀是相同的。

那么,设t[i].fa=j,那么i状态的接受串也必定出现在j状态的接受串的后缀中。又注意到ij的接受串的结束位置是不同的,这样,i状态至少出现了两次。

 

通过这样的规律,通过fa边的拓扑序dp统计答案即可。

还有一点,一个状态的接受串有可能有多种。如aab,b有可能被同一个状态接受。

如果aab出现了n次,那么b也可能出现n次。而aab的任意子串如aa、ab也出现n次。

这样只需要从最长到最短更新dp的值,就可以不漏掉任何情况。

 

WA了很久,一直卡在一个地方。翻了很多题解才有所进展。

做初始化时,dp[i]不能全部置为1,而是当i不是分裂的节点(就是构造函数中的nq)这样的节点才置1。

很容易想到,一个节点分裂了,如果都置1,父节点会重复计算。

但是该分裂节点如何保证正确计数?因为构造中,新分裂的节点nq会成为q的父亲,因此q的计数也会加到nq上,就保证了计数的正确。

 

huyuncong这位大牛写的后缀自动机性质很棒。网址如下:

http://blog.csdn.net/huyuncong/article/details/7583214

引用并补充大牛博客的总结:

一个串的子串有多少之类的问题,或是询问子串/后缀的问题,就用子边转移(自动机性质)。

而计算一个串重复出现次数(right集合的问题),回退到最长匹配状态(LCS问题),就用父边转移(后缀树性质)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define Maxn 250009

int root,last;//sam
int tots;

struct sam_node{
    int fa,son[26];
    int len;
    void init(int _len){len=_len;fa=-1;memset(son,-1,sizeof(son));}
}t[Maxn*2];//length*2

void sam_init(){
    tots=0;
    root=last=0;
    t[tots].init(0);
}

void extend(int w){
    int p=last;
    int np=++tots;t[tots].init(t[p].len+1);
    int q,nq;
    while(p!=-1&&t[p].son[w]==-1){t[p].son[w]=np;p=t[p].fa;}
    if (p==-1) t[np].fa=root;
    else{
        q=t[p].son[w];
        if (t[p].len+1==t[q].len){t[np].fa=q;}
        else{
            nq=++tots;t[nq].init(0);
            t[nq]=t[q];
            t[nq].len=t[p].len+1;
            t[q].fa=nq;t[np].fa=nq;
            while(p!=-1&&t[p].son[w]==q){t[p].son[w]=nq;p=t[p].fa;}
        }
    }
    last=np;
}

int w[Maxn],r[Maxn*2],l;

void topsort(){
    int i;
    for(i=0;i<=l;++i)   w[i]=0;
    for(i=1;i<=tots;++i)w[t[i].len]++;
    for(i=1;i<=l;++i)   w[i]+=w[i-1];
    for(i=tots;i>=1;--i)r[w[t[i].len]--]=i;
    r[0]=0;
}

int dp[Maxn*2],f[Maxn];
char s[Maxn];

int main(){
    int i,p;
    scanf("%s",s);
    l=strlen(s);
    sam_init();
    for(i=0;i<l;++i){
        extend(s[i]-'a');
    }
    topsort();
    
    //for(i=0;i<=tots;++i) {dp[i]=1;}  //wrong initializing
    for(i=0;i<=tots;++i) {dp[i]=0;}
    p=root;
    for(i=0;i<l;++i){
        p=t[p].son[s[i]-'a'];
        dp[p]++;
    }
    for(i=tots;i>=1;--i){
        p=r[i];
        if (t[p].fa!=-1)  dp[t[p].fa]+=dp[p];
    }

    for(i=1;i<=l;++i) f[i]=0;
    for(i=1;i<=tots;++i)
        if (dp[i]>f[t[i].len]) f[t[i].len]=dp[i];
    for(i=l-1;i>=1;--i){
        f[i]=f[i]<f[i+1]?f[i+1]:f[i];
    }

    for(i=1;i<=l;++i){
        printf("%d\n",f[i]);
    }
    return 0;
}




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值