1. 程式人生 > 其它 >字串專題-字尾陣列

字串專題-字尾陣列

習題庫:

第一部分:基礎篇

一些比較模板的題目,有利於熟悉SA的性質

(1)UVA-GATTACA:字尾排序之後求height陣列

(2)SPOJ-Ada and substring:字尾排序求出height陣列之後統計不同的子串的數量(開longlong)

(3)SPOJ-Longest common substring:求兩個串的最長公共子串,預處理height和st表,滑動視窗查詢。

第二部分:提高篇

例題一:P2336 [SCOI2012]喵星球上的點名【莫隊/樹狀陣列 + 字尾陣列】【貼一個BIT寫法的:link

第一問:使用莫隊/BIT做區間數顏色,第二問:也是經典例題,有多少個 \([L,R]\) 區間包含顏色 \(C\),這一步可以在莫隊上差分,也可以使用樹狀陣列【這一類都是資料結構問題了】。

細節部分:

(1)我寫了一個get函式來獲取sa的第i個字元,然後超過ar.size()的時候,必須返回-1,甚至需要返回-inf,因為超過的部分和誰比都是最小的。

(2)題目二分 \([L,R]\) 區間有一個線上的 \(O(n*logn)\)的方法,n是查詢的字串的長度,很爽啊,存都不需要存。

(3)這一次build函式寫的是[0, n)版本的,然後有很多細節也錯了,主要是這句話:

struct SuffixArray {
  int ork[maxn], rk[maxn], osa[maxn], sa[maxn], cnt[maxn];
  void init() { me(ork, 0
), me(rk, 0), me(osa, 0), me(sa, 0), me(cnt, 0); } bool cmp(int x, int y, const int& d, const int& n) { if (ork[x] != ork[y]) return false; if (x + d >= n || y + d >= n) return x + d >= n && y + d >= n; return ork[x + d] == ork[y + d]; } void build(const vector<int
>& ar) { init(); // 清 0 int lim = *max_element(all(ar)), n = ar.size(); for (int i = 0; i < n; i++) cnt[rk[i] = ar[i]]++; for (int i = 1; i <= lim; i++) cnt[i] += cnt[i - 1]; for (int i = 0; i < n; i++) sa[--cnt[rk[i]]] = i; for (int d = 1, top; d < n; d <<= 1, lim = top) { // sort 1 top = 0; for (int i = n - d; i < n; i++) osa[top++] = i; for (int i = 0; i < n; i++) if (sa[i] >= d) osa[top++] = sa[i] - d; // sort 2 for (int i = 0; i <= lim; i++) cnt[i] = 0; for (int i = 0; i < n; i++) cnt[rk[i]]++; for (int i = 1; i <= lim; i++) cnt[i] += cnt[i - 1]; for (int i = n - 1; ~i; i--) sa[--cnt[rk[osa[i]]]] = osa[i]; // upd memcpy(ork, rk, sizeof(rk)), top = 0; for (int i = 0; i < n; i++) rk[sa[i]] = i && cmp(sa[i - 1], sa[i], d, n) ? top - 1 : top++; // !! if (top == n) break; } } void print(const vector<int>& ar) { for (int i = 0; i < ar.size(); i++) { for (int j = sa[i]; j < ar.size(); j++) cout << ar[j] << " "; cout << endl; } } } s; struct DivideBlock { #define BLOCK(x) ((x) / (SIZE)) struct Query { int l, r, i; }; vector<Query> q; int cnt[maxn], sum[maxn], ans[maxn]; int L, R, totAns, qid, SIZE; void init(int sz) { SIZE = sz, me(cnt, 0), me(sum, 0), me(ans, 0); } void addQuery(int l, int r, int i) { q.emp(Query{l, r, i}); } void add(int x) { if (x < 0) return; if (cnt[x]++ == 0) totAns++, sum[x] -= qid; } void del(int x) { if (x < 0) return; if (--cnt[x] == 0) totAns--, sum[x] += qid; } void work(const vector<int>& ar) { init(sqrt(ar.size()) + 1), totAns = L = 0, R = -1; sort(all(q), [&](const Query& a, const Query& b) { if (BLOCK(a.l) != BLOCK(b.l)) return BLOCK(a.l) < BLOCK(b.l); return a.r < b.r; }); for (auto [l, r, i] : q) { if (l > r) continue; while (l < L) add(ar[--L]); while (l > L) del(ar[L++]); while (r < R) del(ar[R--]); while (r > R) add(ar[++R]); qid++, ans[i] = totAns; } while (L <= R) del(ar[L++]); // clear } } b; int n, m, N = 2e4 + 1; vector<int> ar, id, nid; // 在排序裡,超過ar.size 當成 -1 計算,必須設定成為無窮小 inline int get(int x) { return x >= ar.size() ? -1 : ar[x]; } inline void solve() { cin >> n >> m; for (int i = 0, k, x; i < n; i++) { ar.emp(N), id.emp(-1), cin >> k; while (k--) cin >> x, ar.emp(x), id.emp(i); ar.emp(N), id.emp(-1), cin >> k; while (k--) cin >> x, ar.emp(x), id.emp(i); } s.build(ar); for (int i = 0, k; i < m; i++) { cin >> k; int L = 0, R = ar.size(); for (int j = 0, x; j < k; j++) { cin >> x; int l = L, r = R, mid; while (l < r) { mid = (l + r) >> 1; get(s.sa[mid] + j) < x ? l = mid + 1 : r = mid; } L = r, r = R; while (l < r) { mid = (l + r) >> 1; get(s.sa[mid] + j) <= x ? l = mid + 1 : r = mid; } R = r; } b.addQuery(L, R - 1, i); } for (int i = 0; i < ar.size(); i++) nid.emp(id[s.sa[i]]); b.work(nid); for (int i = 0; i < m; i++) cout << b.ans[i] << "\n"; for (int i = 0; i < n; i++) cout << b.sum[i] << " "; }
View Code

例題二:Circle of digits【二分+貪心+O(n)的排序法】

思路的話,一開始也卡了一下,但是發現所有串的長度分成了len和len-1兩類。因為排完序之後我們可以O(1)判斷大小,所以直接二分答案(答案的長度一定是len!)。

例題三:F. Forbidden Indices【單調棧+字尾陣列】【題解

第三部分:與其它知識點的交織

例題一:Suffixes and Palindromes【建圖+差分約束】題解:LINK