【题解】HIHOCODER 1457 后缀自动机四·重复旋律7

请注意,本文编写于 256 天前,最后修改于 256 天前,其中某些信息可能已经过时。

Problem

传送门 >ω<

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

Solution

含有一个串

设 $cnt[x]$ 为 $x$ 状态包含的子串数量
若状态 $x,y$ 有转移 $ch[x][c] = y$ ,则 $sum[y] = sum[x] * 10 + cnt[x] * c$
然后求出 $\sum_{i = 1}^{\text{状态数}}sum[i]$ 即可

显然有 $cnt[x] = l[x] - l[fa[x]]$ ,配合拓扑排序求出 $sum[x]$ 可以在 $O(|S|)$ 的时间内完成

含有多个串

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

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

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

$$sum[y] = sum[x] * 10 + cnt[x] * c$$

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

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

设 $rt$ 状态的子串数量为 1 (空串),然后通过 $ch[x][0 - 9]$ 进行转移

$$cnt[y] += cnt[x] \ \ |\ \ ch[x][c] = y ,c \in 0,1,\cdots,9 $$

通过拓扑同时求出 $cnt , sum$ 即可

代码

#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;
}
Comments

添加新评论