BUAA 533 nanae 是弱小灰(SAM)

转载请注明出处,谢谢http://blog.csdn.net/ACM_cxlove?viewmode=contents    by---cxlove

题意 :给出一个A串,给出若干B串,问A中有多少个不相同的子串是以某个B串为后缀的。

Dshawn找我出个SAM,为了nanae的生日赛。
无聊的跨专业课,就出了这个个题吧~~~~~
对于SAM来说,解法比较简单。
首先将A串和B串都反向之后,后缀便为前缀,题目转变为A串中有多少个不相同的子串是以某个B串为前缀的。
那么以A串建立SAM之后,首先预处理一下,对于A中的每一个子串,存在多少个不同的后继。这就是一个简单的DAG上的DP。得到的结果其实就是,以当前子串为前缀的不同的子串有多少个  (= =这样说的话,问题就解决了。
遍历每一个B串,如果A中存在这个子串,那么就统计一下这个子串有多少个后继,叠加一下就行了。
这样明显有个问题,便是存在重复子串。
大概 就是如果某个结点被统计过,那么他后继中的所有结点的后继,都已经被包括其中。
那么其中一种处理方法便是,处理完之后,把可达的结点标记一下,然后通过DAG关系,依次处理,而且将所有后继中的标记撤消。

那么另外一种处理方法便是,问题的源由在于,B串中某些串是某个串的前缀之类的。那么将B按字典序排序之后,依次处理,如果某个B经过了已经被标记过的结点,那么直接退出。

AekdyCoin的做法是SA,巨巨真可怕,不过不得不说SA的做法好麻烦,耗时也多。ORZ


#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define LL long long
using namespace std;
const int N=210005;
struct Node{
    char word[105];
    bool operator<(const Node n)const{
        return strcmp(word,n.word)<0;
    }
}a[1005];
struct SAM  {
    SAM *pre,*son[26];
    int f,len;
    LL cnt;
}*root,*tail,que[N],*b[N];
char str[N];
int cnt[N];
int tot=0;
int c[N];
int n;
void add(int c,int l){
    SAM *p=tail,*np=&que[tot++];
    np->len=l;
    while(p&&p->son[c]==NULL) p->son[c]=np,p=p->pre;
    if(p==NULL) np->pre=root;
    else{
        SAM *q=p->son[c];
        if(p->len+1==q->len) np->pre=q;
        else{
            SAM *nq=&que[tot++];
            *nq=*q;
            nq->len=p->len+1;
            np->pre=q->pre=nq;
            while(p&&p->son[c]==q) p->son[c]=nq,p=p->pre;
        }
    }
    tail=np;
}
LL ans=0;
int main(){
//    freopen("1.in","r",stdin);
//    freopen("output.txt","w",stdout);
    root=tail=&que[tot++];
    scanf("%s",str);
    int l=strlen(str);
    for(int i=0;i<l/2;i++) swap(str[i],str[l-1-i]);
    for(int i=0;str[i];i++) add(str[i]-'a',i+1);
    for(int i=0;i<tot;i++) c[que[i].len]++;
    for(int i=1;i<tot;i++) c[i]+=c[i-1];
    for(int i=0;i<tot;i++) b[--c[que[i].len]]=&que[i];
    for(int i=0;i<tot;i++) que[i].cnt=1;
    for(int i=tot-1;i>=0;i--){
        SAM *p=b[i];
        for(int j=0;j<26;j++){
            if(p->son[j])
                p->cnt+=p->son[j]->cnt;
        }
    }
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%s",a[i].word);
        int l=strlen(a[i].word);
        for(int j=0;j<l/2;j++)
            swap(a[i].word[j],a[i].word[l-1-j]);
    }
    sort(a,a+n);
    for(int j=0;j<n;j++){
        SAM *p=root;
        bool flag=true;
        for(int i=0;a[j].word[i]&&flag;i++){
            int s=a[j].word[i]-'a';
            if(p->son[s]==NULL) flag=false;
            else{
                if(p->son[s]->f) flag=false;
                p=p->son[s];
            }
        }
        if(flag){
            ans+=p->cnt;
            p->f=1;
        }
    }
    printf("%lld\n",ans);
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值