P4081 [USACO17DEC]Standing Out from the Herd P
知識點: 廣義 SAM
原題面 Luogu
「扯」
隨便「口胡」一下居然「過」了。
比較考驗「程式碼能力」,第一次感覺「大模擬」沒有白寫(((
還有這個「符號」實在是太「上頭」了。
前置知識
線上構造廣義 SAM,推薦:【學習筆記】字串—廣義字尾自動機 - 辰星凌
題意簡述
給定 \(n\) 個僅包含小寫字母的字串 \(S_1\sim S_n\)。
定義字串 \(S_i\) 的 「獨特值」為只屬於該串的本質不同的非空子串的個數。
求字串 \(S_1\sim S_n\) 的「獨特值」。
\(1\le n\le 10^5, 1\le \sum|S_i|\le 10^5\)。
分析題意
多串子串問題,考慮廣義 SAM。
若 \(n\) 較小,直接維護每個狀態 包含幾個串的資訊。
parent 樹上 DP 更新祖先資訊,直接更新答案即可。
但 \(n\le 10^5\) 空間爆炸,做不得,考慮亂搞一波。
用 string 存字串,建廣義 SAM。
在動態建立 SAM 時,\(\operatorname{only}_i\) 記錄每個狀態 \(i\) 包含哪一個串的資訊。
若某狀態包含多個串資訊,則\(\operatorname{only}_i = - 1\)。
parent 樹上 DP 更新祖先資訊。
先考慮一波 無腦暴力:
對於 \(S_i\),列舉其所有子串,在 SAM 上跑出對應狀態 \(u\)。
若有 \(\operatorname{only}_u\not = 1\)
這樣的子串數,即為 \(S_i\) 的「獨特值」。
上述演算法瓶頸是列舉子串。發現字首有一些好性質:
- 連續字首 對應狀態在 SAM 上也是連續的,實現時直接把串扔到 SAM 上跑 即得對應狀態。
- 字首對應狀態到 parent 樹根的鏈上 包含該字首所有後綴,可以包含所有子串資訊。
考慮僅列舉字首,列舉複雜度變為線性。
發現一些結論:
- parent 樹上一條從葉到根的鏈,維護的串的個數,是單調不減的。
- 若某狀態 \(i\) 的 \(\operatorname{only}_i\not = -1\), 則對於其子樹中所有狀態 \(j\),有 \(\operatorname{only}_j=\operatorname{only}_i\)
考慮維護狀態 \(i\) 的 parent 樹上距它最遠的,\(\operatorname{only}\not= -1\) 的祖先 \(\operatorname{top}_i\)。
由結論 1,若某狀態 \(i\) 的 \(\operatorname{only}_i=-1\),\(\operatorname{top}_i=0\)。
由結論 2,跑到的 \(\operatorname{only}\not= - 1\) 的節點對答案有貢獻,在其子樹中所有節點都會有貢獻。
可預處理子樹 \(\operatorname{len}(i)-\operatorname{len}(\operatorname{link}(i))\) 之和 \(\operatorname{sum}\),即某子樹中的子串數量。
統計答案時,統計 跑到的狀態 \(u\) 的 \(\operatorname{sum}(\operatorname{top}_u)\),一個 \(\operatorname{top}_u\) 只能統計一次,去重可用排序實現。
這樣為什麼是對的?考慮字首的性質 2 感性理解一下。
每個串都在 SAM 上進行一匹配 並進行一次排序,總複雜度大概是 \(O(\sum\limits_{i}^{n} |S_i|\log |S_i|)\),可過。
程式碼實現
//知識點:廣義 SAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define ll long long
#define pr std::pair
#define mp std::make_pair
const int kMaxn = 2e5 + 10;
const int kMaxm = 26;
//=============================================================
std :: string S[kMaxn];
int cnt[kMaxn], id[kMaxn << 1];
int only[kMaxn << 1], top[kMaxn << 1], sum[kMaxn << 1];
int num, node_num = 1, ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
int edge_num, head[kMaxn], v[kMaxn << 1], ne[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 AddEdge(int u_, int v_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
int Insert(int c_, int last_) {
if (ch[last_][c_]) {
int p = last_, q = ch[p][c_];
if (len[p] + 1 == len[q]) {
only[q] = - 1;
return q;
}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
len[newq] = len[p] + 1;
link[newq] = link[q];
link[q] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
only[newq] = num;
return newq;
}
int p = last_, now = ++ node_num;
only[now] = num;
len[now] = len[p] + 1;
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] + 1) {link[now] = q; return now;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
return now;
}
void Dfs1(int u_) {
for (int i = head[u_]; i; i = ne[i]) {
Dfs1(v[i]);
if (only[u_] == - 1) continue ;
if (! only[u_]) {
only[u_] = only[v[i]];
} else if (only[u_] != only[v[i]]) {
only[u_] = - 1;
}
}
}
void Dfs2(int u_, int top_) {
if (! top_ && only[u_] != - 1) top_ = u_;
top[u_] = top_;
if (top_) sum[u_] += len[u_] - len[link[u_]]; //你的字首和
for (int i = head[u_]; i; i = ne[i]) {
Dfs2(v[i], top_);
sum[u_] += sum[v[i]];
}
}
void Work(std :: string S_) {
std :: vector <int> node;
ll ans = 0;
int n = S_.length(), now = 1;
for (int i = 0; i < n; ++ i) {
now = ch[now][S_[i] - 'a'];
if (only[now] != - 1) node.push_back(top[now]);
}
std :: sort(node.begin(), node.end());
for (int i = 0, n = node.size(); i < n; ++ i) {
if (i != 0) {
if (node[i] == node[i - 1]) continue;
}
ans += sum[node[i]];
}
printf("%lld\n", ans);
}
//=============================================================
int main() {
int T = read();
for (num = 1; num <= T; ++ num) {
std :: cin >> S[num];
int n = S[num].length(), last = 1;
for (int j = 0; j < n; ++ j) last = Insert(S[num][j] - 'a', last);
}
for (int i = 2; i <= node_num; ++ i) AddEdge(link[i], i);
Dfs1(1), Dfs2(1, 0);
for (int i = 1; i <= T; ++ i) Work(S[i]);
return 0;
}