HIHOCODER 1457 后缀自动机四·重复旋律7 SAM

更好的阅读体验:Press Here

Problem

传送门 >ω<

题目大意:
给定 n n 个由 09 组成的数字串,问本质不同串的总和是多少

Solution

含有一个串

cnt[x] c n t [ x ] x x 状态包含的子串数量
若状态 x,y 有转移 ch[x][c]=y c h [ x ] [ c ] = y ,则 sum[y]=sum[x]10+cnt[x]c s u m [ y ] = s u m [ x ] ∗ 10 + c n t [ x ] ∗ c
然后求出 i=1sum[i] ∑ i = 1 状态数 s u m [ i ] 即可

显然有 cnt[x]=l[x]l[fa[x]] c n t [ x ] = l [ x ] − l [ f a [ x ] ] ,配合拓扑排序求出 sum[x] s u m [ x ] 可以在 O(|S|) O ( | S | ) 的时间内完成

含有多个串

在 SAM 中插入多个串需要利用特殊字符将其隔开,防止出现本不应该出现的子串

考虑多个串之间用 ‘:’ 隔开(ASCII 刚好是 ‘0’ + 10 或者说 ‘9’ + 1,比较方便)

然后观察原来的转移方程:

sum[y]=sum[x]10+cnt[x]c s u m [ y ] = s u m [ x ] ∗ 10 + c n t [ x ] ∗ c

但是发现现在的子串中有可能包含 ‘:’ ,显然每个状态含有 ‘:’ 的子串是不能算在 cnt[x] c n t [ x ] 里面的,所以现在的问题就是如何求出每个状态不带 ‘:’ 的子串数量

SAM 是一个天生的 DAG ,可以用动态规划的形式来求出每个点的 cnt c n t ,也就是不包括 ‘:’ 的子串数量

rt r t 状态的子串数量为 1 (空串),然后通过 ch[x][09] c h [ x ] [ 0 − 9 ] 进行转移

cnt[y]+=cnt[x]  |  ch[x][c]=y,c0,1,,9 c n t [ y ] + = c n t [ x ]     |     c h [ x ] [ c ] = y , c ∈ 0 , 1 , ⋯ , 9

通过拓扑同时求出 cnt,sum c n t , s u m 即可

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2000010;
const int mod = 1000000007;
int read() {
    int ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = -1; ch = getchar();}
    while(ch <= '9' && ch >= '0') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
struct SAM {
    int ch[N << 1][11] , fa[N << 1] , l[N << 1] , sum[N << 1] , bac[N << 1] , T[N] , d[N << 1];
    int cnt = 1 , last = 1;
    void ins(int c) {
        int x = last , nx = ++ cnt; last = nx;
        l[nx] = l[x] + 1;
        for(; x && !ch[x][c] ; x = fa[x]) ch[x][c] = nx;
        if(!x) fa[nx] = 1;
        else {
            int y = ch[x][c];
            if(l[y] == l[x] + 1) fa[nx] = y;
            else {
                int ny = ++ cnt; l[ny] = l[x] + 1;
                memcpy(ch[ny] , ch[y] , sizeof(ch[y]));
                fa[ny] = fa[y]; fa[y] = fa[nx] = ny;
                for(; x && ch[x][c] == y ; x = fa[x]) ch[x][c] = ny;
            }
        }
    } 
    void insert(char * s) {
        int n = strlen(s);
        for(int i = 0 ; i < n ; ++ i) ins(s[i] - '0');
        ins(10);
    }
    void work() {
        for(int i = 1 ; i <= cnt ; ++ i) ++ bac[l[i]];
        for(int i = 1 ; i <= 2000000 ; ++ i ) bac[i] += bac[i - 1];
        for(int i = 1 ; i <= cnt ; ++ i) T[bac[l[i]] --] = i;

        d[1] = 1;
        int ans = 0;
        for(int i = 1 ; i <= cnt ; ++ i) {
            int x = T[i];
            ans = (ans + sum[x]) % mod;
            for(int j = 0 ; j < 10 ; ++ j) {
                if(ch[x][j]) {
                    d[ch[x][j]] = (d[ch[x][j]] + d[x]) % mod;
                    sum[ch[x][j]] = (sum[ch[x][j]] + (1ll * sum[x] * 10 % mod + 1ll * d[x] * j % mod) % mod) % mod;
                }
            }
        }
        printf("%d\n" , ans);
    }
}sam;
char s[N];
int main() {
    int t = read();
    while(t --) {
        scanf("%s" , s);
        sam.insert(s);
    }
    sam.work();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值