「SDOI2016」生成魔咒
阿新 • • 發佈:2020-08-17
知識點: SAM
原題面 Loj Luogu
分析題意
求 \(S\) 的所有字首的本質不同的子串的個數。
考察對 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; }