1. 程式人生 > >bzoj 2754 [SCOI2012]喵星球上的點名 字尾陣列+莫隊

bzoj 2754 [SCOI2012]喵星球上的點名 字尾陣列+莫隊

題面

題目傳送門

解法

之前曾嘗試用AC自動機暴力水過去,然而T了……

  • AC自動機顯然是可以實現的,但是因為字符集太大,所以會導致超時。
  • 那麼我們考慮字尾陣列解決。首先我們可以將所有串整個拼成一個新的字串。名和姓之間用一種分隔符隔開,不同的字串之間用另一種分隔符隔開。
  • 然後構造出字尾陣列。考慮如何求解第一問,顯然可以在後綴陣列中找到以當前字串開頭的字尾的位置,然後上下二分一下對應的區間[l,r][l,r]滿足排序後區間[l,r][l,r]對應的字尾的字首都為當前詢問的字串。然後就變成求解一個區間中有多少個不同的數的問題了,可以使用樹狀陣列。此處使用莫隊,因為這樣方便求解第二問。
  • 然後我們考慮如何求解第二問。在莫隊的時候,對於新增和刪除分別處理。我們可以記錄當前這個數上一次出現的位置在哪裡。如果這個數在當前詢問的區間中被刪掉了,那麼更新這個數的答案。如果這個數是第一次被新增進這個區間,那麼更新這個數上一次出現的位置。
  • 時間複雜度:O(nlogn+nm)O(n\log n+n\sqrt m)

程式碼

#include <bits/stdc++.h>
#define N 300010
using namespace std;
template <typename T> void read(T &x) {
	x = 0; int
f = 1; char c = getchar(); while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();} while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f; } struct Node {int l, r, id;} a[N]; int n, m, sum, c[N], s[N], y[N], sa[N], st[N], ans[N], las[N], len[N], num[N], pos[N], rnk[N], f[N][21]; bool cmp
(Node a, Node b) {return (a.l >> 9) == (b.l >> 9) ? a.r < b.r : a.l < b.l;} void del(int x, int y) {s[c[x]]--; if (!s[c[x]]) sum--, num[c[x]] += y - las[c[x]];} void ins(int x, int y) {if (!s[c[x]]) sum++, las[c[x]] = y; s[c[x]]++;} int lcp(int i, int j) { int l = i + 1, r = j, t = log2(r - l + 1); return min(f[l][t], f[r - (1 << t) + 1][t]); } void Sort() { for (int i = 1; i <= m; i++) s[i] = 0; for (int i = 1; i <= n; i++) s[rnk[i]]++; for (int i = 1; i <= m; i++) s[i] += s[i - 1]; for (int i = n; i; i--) sa[s[rnk[y[i]]]--] = y[i]; } void build() { for (int i = 1; i <= n; i++) rnk[i] = st[i], y[i] = i; m = 1e4 + 10, Sort(); int len = 0; for (int k = 1; k <= n; k <<= 1, m = len, len = 0) { for (int i = n - k + 1; i <= n; i++) y[++len] = i; for (int i = 1; i <= n; i++) if (sa[i] > k) y[++len] = sa[i] - k; Sort(), swap(rnk, y), rnk[sa[1]] = len = 1; for (int i = 2; i <= n; i++) rnk[sa[i]] = y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k] ? len : ++len; if (len >= n) break; } for (int i = 1, k = 0; i <= n; i++) { if (rnk[i] == 1) continue; if (k) k--; int j = sa[rnk[i] - 1]; while (st[i + k] == st[j + k]) k++; f[rnk[i]][0] = k; } for (int j = 1; (1 << j) <= n; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]); } int main() { int tot, q; read(tot), read(q); for (int i = 1; i <= tot; i++) { int l; read(l); while (l--) read(st[++n]); st[++n] = 1e4 + 1, read(l); while (l--) read(st[++n]); st[++n] = 1e4 + 2; } int tmp = n, cnt = 1; for (int i = 1; i <= q; i++) { int l; read(l); pos[i] = n + 1, len[i] = l; while (l--) read(st[++n]); if (i != q) st[++n] = 1e4 + 2; } build(); for (int i = 1; i <= tmp; i++) if (st[i] == 1e4 + 2) cnt++; else c[rnk[i]] = cnt; for (int i = 1; i <= q; i++) { int x = rnk[pos[i]], L = len[i]; int l = 1, r = x - 1, tl = x; while (l <= r) { int mid = (l + r) >> 1; if (lcp(mid, x) >= L) tl = mid, r = mid - 1; else l = mid + 1; } l = x + 1, r = n; int tr = x; while (l <= r) { int mid = (l + r) >> 1; if (lcp(x, mid) >= L) tr = mid, l = mid + 1; else r = mid - 1; } a[i] = (Node) {tl, tr, i}; } sort(a + 1, a + q + 1, cmp); memset(s, 0, sizeof(s)), sum = 0; for (int i = 1, l = 1, r = 0; i <= q; i++) { while (r < a[i].r) ins(++r, i); while (r > a[i].r) del(r--, i); while (l < a[i].l) del(l++, i); while (l > a[i].l) ins(--l, i); ans[a[i].id] = sum - (s[0] > 0); } for (int i = 1; i <= q; i++) cout << ans[i] << "\n"; for (int i = 1; i <= tot; i++) if (s[i]) num[i] += q - las[i] + 1; for (int i = 1; i <= tot; i++) cout << num[i] << ' '; cout << "\n"; return 0; }