「SDOI2016」生成魔咒
知识点: SAM
原题面 Loj Luogu
题意简述
求 \(S\) 的所有前缀的本质不同的子串的个数。
\(S\le 10^5, |\sum|\le 10^9\)
分析题意
考察对 SAM 构建过程的理解。
对于一个确定的字符串 \(S\),其本质不同子串的个数,等于所有状态所表示子串的个数之和。
即有下式:
\[ans = \sum_{u\in \operatorname{DAWG}}{\operatorname{len(u)} - \operatorname{len(\operatorname{link}(u))}}
\]
对于字符串 \(S\),考虑新加入字符 \(c\) 的影响。
加入 \(c\) 后,显然答案增加 不在 \(S\) 中出现的 \(S+c\) 后缀的个数。
设表示 \(S+c\) 的状态为 \(a\),考虑第一个在 \(S\) 中出现的 \(S+c\) 的后缀,会在 SAM 构建中赋值给 \(\operatorname{link}(a)\) 上。
则新字符的贡献即为 \(\operatorname{len}(a) - \operatorname{len}(\operatorname{link}(a))\)。
感觉在 SDOI 见了不少模板题了,传统艺能?
代码实现
//知识点:SAM
/*
By:Luckyblock
*/
#include <map>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define ll long long
const int kMaxn = 1e5 + 10;
//=============================================================
int n, last = 1, node_num = 1, link[kMaxn << 1];
std :: map <int, int> ch[kMaxn << 1];
ll ans, len[kMaxn << 1];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
int Insert(int c_) {
int p = last, now = last = ++ node_num;
len[now] = len[p] + 1ll;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return now;}
int q = ch[p][c_];
if (len[q] == len[p] + 1ll) {link[now] = q; return now;}
int newq = ++ node_num;
ch[newq] = ch[q];
link[newq] = link[q], len[newq] = len[p] + 1ll;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return now;
}
//=============================================================
int main() {
int n = read();
for (int i = 1; i <= n; ++ i) {
int x = read(), now = Insert(x);
printf("%lld\n", ans += (len[now] - len[link[now]]));
}
return 0;
}
作者@Luckyblock,转载请声明出处。