hdu4436 str2int 后缀自动机 SAM

         给n个串,求这n个串的的不同子串表示的数字的和对2012取模...看到关于什么子串,后缀的第一反应应该就是后缀数组或者后缀自动机..10^5的规模,SA,SAM应该都能做,SAM应该好像点。把n各串用某拼接符连起来建立一个SAM,因为在SAM上跑一边可以求出这个串的所有子串,所以可以利用这个性质来求不同子串的和。先做一个拓扑排序,然后从前到后扫一遍,每个状态维护两个值cnt,sum分别表示到达该状态的方案数和当前的子串和,每次枚举一条转移边,假设这条边从p到q,那么就执行q.cnt+=p.cnt,q.sum+=p.sum*10+转移边*p.cnt;最后统计一下就行了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <memory.h>
#include <string>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn=100000+50000;
const int S=11;
const int mod=2012;
int tot,len;
int n,m;
int wtop[maxn<<1];
int c[maxn<<1];
int k;
int ans;
struct node
{
    node *par,*go[S];
    int val,id,cnt,sum;
}*tail,*root,que[maxn<<1],*top[maxn<<1];
char str[maxn],s[maxn],ss[maxn];
struct SAM
{
    void init()
    {
        memset(que,0,sizeof que);
        tot=0;
        len=1;
        root=tail=&que[tot++];
        root->id=root->sum=root->cnt=0;
        ans=0;
    }
    void add(int c,int l)
    {
        node* p=tail;
        node* np=&que[tot++];
        np->val=l;
        np->id=tot-1;
        np->cnt=np->sum=0;
        while(p && p->go[c]==NULL) p->go[c]=np,p=p->par;
        if (p==NULL) np->par=root;
        else
        {
            node* q=p->go[c];
            if (p->val+1==q->val) np->par=q;
            else
            {
                node *nq=&que[tot++];
                *nq=*q;
                nq->id=tot-1;
                nq->val=p->val+1;
                np->par=q->par=nq;
                nq->cnt=nq->sum=0;
                while(p && p->go[c]==q) p->go[c]=nq,p=p->par;

            }
        }
        tail=np;
    }
    void debug_suff()
    {
        for (int i=0; i<tot; i++)
        {
            for (int c=0; c<S; c++)
            if (que[i].go[c])
            {
                cout<<que[i].id<<" "<<que[i].go[c]->id<<endl;
            }
        }
    }
    void debug_pre()
    {
        for (int i=1; i<tot; i++)
        {
            cout<<que[i].id<<" "<<que[i].par->id<<endl;
        }
    }
    void TopS()
    {
        memset(c,0,sizeof c);
        for (int i=0; i<tot; i++)
        c[que[i].val]++;
        for (int i=1; i<len; i++)
        c[i]+=c[i-1];
        for (int i=0; i<tot; i++)
        top[--c[que[i].val]]=&que[i],wtop[c[que[i].val]]=i;
    }
    int slove()
    {
        TopS();
        node *p,*q;
        root->cnt=1;
        root->sum=0;
         int res=0;
        for (int i=0; i<tot; i++)
        {
            p=top[i];
            for (int j=0; j<10; j++)
            if (i==0 && j==0) continue;
            else
            {
                if (p->go[j])
                {
                    q=p->go[j];
                    q->cnt+=p->cnt;
                    q->cnt%=mod;
                    q->sum+=(p->sum*10+p->cnt*j);
                    q->sum%=mod;
                }
            }
//            res+=p->sum;
//            res%=mod;
        }

        for (int i=0; i<tot; i++)
        {
            res+=que[i].sum;
            res%=mod;
        }
        return res;
    }
}sam;
int main()
{
//    freopen("in.txt","r",stdin);
    while(~scanf("%d",&n))
    {
        sam.init();
        for (int i=1; i<=n; i++)
        {
            scanf("%s",str);
            int ll=strlen(str);
            for (int j=0; j<ll; j++)
            sam.add(str[j]-'0',len++);
            sam.add(10,len++);
        }
        cout<<sam.slove()<<endl;


    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值