「SDOI 2016」生成魔咒

题目链接:LOJ 2033

魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示。例如可以将魔咒字符 $1$、$2$ 拼凑起来形成一个魔咒串 $[1, 2]$。

一个魔咒串 $S$ 的非空子串被称为魔咒串 $S$ 的生成魔咒。

例如 $S = [1, 2, 1]$ 时,它的生成魔咒有 $[1]$、$[2]$、$[1, 2]$、$[2, 1]$、$[1, 2, 1]$ 五种。$S = [1, 1, 1]$ 时,它的生成魔咒有 $[1]$、$[1, 1]$、$[1, 1, 1]$ 三种。

最初 $S$ 为空串。共进行 $n$ 次操作,每次操作是在 $S$ 的结尾加入一个魔咒字符。每次操作后都需要求出,当前的魔咒串 $S$ 共有多少种生成魔咒。

数据范围:$1 \le n \le 10 ^ 5$,$1 \le \Sigma \le 10 ^ 9$。


Solution

我们对字符串 $S$ 建立后缀自动机,由于 $\text{SAM}$ 是在线的,那么每次插入一个字符后,产生的贡献就是节点 $last$ 的贡献。而这个贡献就是其父节点无法表示出的子串数量,即 $len(last) - len(link(last))$。

因为字符集大小为 $10 ^ 9$,因此需要使用 $\text{map}$ 维护转移。

时间复杂度:$\mathcal O(n \log \Sigma)​$。


Code

#include <cstdio>
#include <map>

const int N = 1e5 + 5;

int n;

template <int N>
struct SAM {
    int n, lst;
    struct State {
        int len, lnk;
        std::map<int, int> nxt;
    } st[N << 1];
    SAM() {
        st[n = lst = 0].lnk = -1;
    }
    void extend(int x) {
        int cur = ++n;
        st[cur].len = st[lst].len + 1;
        int p = lst;
        for (; ~p && !st[p].nxt.count(x); p = st[p].lnk) {
            st[p].nxt[x] = cur;
        }
        if (p == -1) {
            st[cur].lnk = 0;
        } else {
            int q = st[p].nxt[x];
            if (st[q].len == st[p].len + 1) {
                st[cur].lnk = q;
            } else {
                int clone = ++n;
                st[clone].len = st[p].len + 1;
                st[clone].lnk = st[q].lnk;
                st[clone].nxt = st[q].nxt;
                for (; ~p && st[p].nxt[x] == q; p = st[p].lnk) {
                    st[p].nxt[x] = clone;
                }
                st[cur].lnk = st[q].lnk = clone;
            }
        }
        lst = cur;
    }
};

SAM<N> S;

int main() {
    scanf("%d", &n);
    long long ans = 0;
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        S.extend(x);
        int u = S.lst, v = S.st[u].lnk;
        ans += S.st[u].len - S.st[v].len;
        printf("%lld\n", ans);
    }
    return 0;
}

发表评论