題目傳送門:LOJ #3049。數組
題意簡述:
給定一個長度爲 $n$ 的母串 $S$。數據結構
有 $n_a$ 個 A 類串,都是 $S$ 的子串,以區間的形式給出。優化
有 $n_b$ 個 B 類串,都是 $S$ 的子串,以區間的形式給出。spa
有 $m$ 個支配關係,形式爲第 $i$ 個 A 類串支配第 $j$ 個 B 類串。code
你須要求出最長的字符串 $T$ 的長度,使得 $T$ 能夠被劃分爲若干個 A 類串的拼接,而且相鄰兩個 A 類串 $t_i$ 和 $t_{i+1}$ 知足 $t_i$ 支配某個 B 類串 $j$,而 $j$ 是 $t_{i+1}$ 的前綴。排序
若是 $T$ 能夠無限長,輸出 -1
。字符串
題解:
若是咱們設法從每一個 B 類串向存在前綴爲這個 B 類串的 A 類串連邊,則顯然如有環則答案能夠無限大,若無環則能夠作一遍 DAG DP 獲得答案。get
然而咱們並不能直接實現這一過程,且不論可否快速肯定前綴從屬關係,這類邊的條數就能達到 $\mathcal{O}(n_an_b)$ 級別。string
當邊數太多時,首先應該想到的就是數據結構優化建邊,典型的例子是樹狀結構優化建邊,例如線段樹和後綴樹。it
實際上這題能夠直接使用後綴樹優化建邊,邊數只有線性級別,可是過程仍是 $\mathcal{O}(n\log n)$ 的。至於建後綴樹能夠直接建也能夠對反串建 SAM。
對於我這種不會後綴樹,SAM 理解也不深的菜兔,仍是選擇了後綴數組。
首先建出後綴數組,這個你們都會。而後對每一個 B 類串考慮如何連邊。
假設咱們已經對每一個 A 類和 B 類串在母串上定位了,那麼包含某個 B 類串做爲前綴的 A 類串須要知足兩個條件:
該 A 串的左端點對應的後綴和 B 串的左端點對應的後綴的 LCP 長度應該大於 B 串的長度,而且該 A 串的長度也應該大於 B 串的長度。
第一個條件能夠轉化爲對應着後綴數組上的一段區間,具體根據 Height 數組肯定,這一步建出 ST 表後二分能夠在 $\mathcal{O}(\log n)$ 的時間內解決。
第二個條件彷佛沒有什麼巧妙的辦法。不過咱們能夠考慮按照長度從大到小的順序加入每一個串,這樣就可以扔掉第二個限制了。
因此具體的思路就是按照長度從大到小排序插入 A 類串,在 B 類串和數據結構之間建邊,使用合適的數據結構維護歷史版本和區間。
很顯然,主席樹即是合適的數據結構,主席樹優化建邊,樹內邊數 $\mathcal{O}(n_a\log n)$,樹外邊數 $\mathcal{O}(n_b\log n)$。
接下來是代碼,複雜度 $\mathcal{O}((n+n_a+n_b)\log n)$:
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> typedef long long LL; const int MN = 200005; const int MS = 4200005; char str[MN]; int N, Sig, rk[MN], SA[MN], SA2[MN], buk[MN], tmp[MN]; int Height[MN]; inline void getHeight() { for (int i = 1, k = 0; i <= N; ++i) { if (rk[i] == 1) { Height[rk[i]] = k = 0; continue; } if (k) --k; int j = SA[rk[i] - 1]; while (i + k <= N && j + k <= N && str[i + k] == str[j + k]) ++k; Height[rk[i]] = k; } } inline void RSort() { for (int i = 1; i <= Sig; ++i) buk[i] = 0; for (int i = 1; i <= N; ++i) ++buk[rk[i]]; for (int i = 1; i <= Sig; ++i) buk[i] += buk[i - 1]; for (int i = N; i >= 1; --i) SA[buk[rk[SA2[i]]]--] = SA2[i]; } inline void getSA(char *str) { Sig = 127, rk[N + 1] = 0; for (int i = 1; i <= N; ++i) rk[i] = str[i], SA2[i] = i; RSort(); for (int j = 1; j <= N; j <<= 1) { int p = 0; for (int i = N - j + 1; i <= N; ++i) SA2[++p] = i; for (int i = 1; i <= N; ++i) if (SA[i] > j) SA2[++p] = SA[i] - j; RSort(); tmp[SA[1]] = p = 1; for (int i = 2; i <= N; ++i) { int lst = SA[i - 1], now = SA[i]; if (rk[lst] != rk[now] || rk[lst + j] != rk[now + j]) ++p; tmp[SA[i]] = p; } for (int i = 1; i <= N; ++i) rk[i] = tmp[i]; if ((Sig = p) == N) break; } getHeight(); } int Lg[MN]; inline void Log(int N) { Lg[0] = -1; for (int i = 1; i <= N; ++i) Lg[i] = Lg[i >> 1] + 1; } int ST[MN][18]; inline void InitST() { for (int i = 2; i <= N; ++i) ST[i][0] = Height[i]; for (int j = 1; j <= Lg[N - 1]; ++j) { for (int i = 1; i <= 1 << j; ++i) ST[i][j] = 0; for (int i = 1 << j | 1; i <= N; ++i) ST[i][j] = std::min(ST[i - (1 << (j - 1))][j - 1], ST[i][j - 1]); } } int NA, la[MN], ra[MN]; int NB, lb[MN], rb[MN]; struct sub { int lb, len, typ, id; sub() {} sub(int lb, int len, int typ, int id) : lb(lb), len(len), typ(typ), id(id) {} inline friend bool operator <(sub i, sub j) { return i.len == j.len ? i.typ < j.typ : i.len > j.len; } } substrs[MN * 2]; int d[MS]; std::vector<int> G[MS]; inline void addEdge(int x, int y) { ++d[y]; G[x].push_back(y); } int rt[MN], lc[MS], rc[MS], wgh[MS], cnt; void Mdf(int &rt, int l, int r, int p, int x) { lc[++cnt] = lc[rt], rc[cnt] = rc[rt]; if (rt) addEdge(cnt, rt); wgh[rt = cnt] = 0; if (l == r) { addEdge(rt, x); return ; } int mid = (l + r) >> 1; if (p <= mid) Mdf(lc[rt], l, mid, p, x), addEdge(rt, lc[rt]); else Mdf(rc[rt], mid + 1, r, p, x), addEdge(rt, rc[rt]); } void Edg(int rt, int l, int r, int a, int b, int x) { if (!rt || r < a || b < l) return ; if (a <= l && r <= b) { addEdge(x, rt); return ; } int mid = (l + r) >> 1; Edg(lc[rt], l, mid, a, b, x); Edg(rc[rt], mid + 1, r, a, b, x); } int que[MS], l, r; LL f[MS]; int main() { int T; scanf("%d", &T); Log(200000); while (T--) { scanf("%s", str + 1); N = strlen(str + 1); getSA(str); InitST(); scanf("%d", &NA); for (int i = 1; i <= NA; ++i) scanf("%d%d", &la[i], &ra[i]), substrs[i] = sub(rk[la[i]], ra[i] - la[i] + 1, 0, i); scanf("%d", &NB); for (int i = 1; i <= NB; ++i) scanf("%d%d", &lb[i], &rb[i]), substrs[NA + i] = sub(rk[lb[i]], rb[i] - lb[i] + 1, 1, i); std::sort(substrs + 1, substrs + NA + NB + 1); cnt = NA + NB; for (int i = 1; i <= NA; ++i) wgh[i] = ra[i] - la[i] + 1; for (int i = 1; i <= NB; ++i) wgh[NA + i] = 0; for (int i = 1, gen = 0; i <= NA + NB; ++i) { sub p = substrs[i]; if (!p.typ) ++gen, Mdf(rt[gen] = rt[gen - 1], 1, N, p.lb, p.id); else { int Lb = p.lb, Rb = p.lb; for (int j = Lg[p.lb - 1]; ~j; --j) if (ST[Lb][j] >= p.len) Lb -= 1 << j; for (int j = Lg[N - p.lb]; ~j; --j) if (Rb + (1 << j) <= N && ST[Rb + (1 << j)][j] >= p.len) Rb += 1 << j; Edg(rt[gen], 1, N, Lb, Rb, NA + p.id); } } int M; scanf("%d", &M); for (int i, j; M--; ) { scanf("%d%d", &i, &j); addEdge(i, NA + j); } LL Ans = 0; l = 1, r = 0; for (int i = 1; i <= cnt; ++i) { f[i] = wgh[i]; if (!d[i]) que[++r] = i; } while (l <= r) { int u = que[l++]; Ans = std::max(Ans, f[u]); for (auto v : G[u]) { f[v] = std::max(f[v], f[u] + wgh[v]); if (!--d[v]) que[++r] = v; } } if (r != cnt) puts("-1"); else printf("%lld\n", Ans); for (int i = 1; i <= cnt; ++i) d[i] = 0, G[i].clear(); } return 0; }
實際上,一樣是後綴數組預處理,關於建邊有更好的方法,好比用後綴數組建出後綴樹,而後在後綴樹上亂搞建邊,這樣是線性的。