【BZOJ5253】【2018多省省隊聯測】制胡竄
阿新 • • 發佈:2019-02-07
【題目連結】
【思路要點】
- 首先,一個詢問的答案只和詢問串的在主串中所有出現的位置有關。
- 直接求解所有出現位置顯然會超時,一種可行的方法是用離線詢問+字尾自動機(樹)+線段樹合併來維護出現位置的右端點集合。
- 先解決一個小問題:定位一個詢問可以在後綴樹上倍增在\(O(LogN)\)的時間內完成。
- 現在我們有了一棵維護著所有詢問串出現位置的右端點的線段樹,考慮如何得到答案。
- 考慮計算出所有的兩個斷點把所有出現位置都切斷的方案數,再用\(\binom{N-1}{2}\)減去它。
- 考慮較靠前的斷點切斷了哪些字串。
- 我們先排除一些特殊情況:
- 1、較靠前的斷點不切斷任意的字串,那麼它一定在詢問串第一次出現之前,並且第二個斷點要恰好切斷所有字串,也即第二個斷點的取值範圍是詢問串所有出現位置的並。
- 2、較靠前的斷點切斷了所有的字串,那麼較靠後的端點只需要滿足在較靠前的斷點之後即可,因此在這種情況下可能的方案數是一個等差數列的各項之和。
- 除了上述情況外,我們需要求出的即是:\(\sum_{i=ql}^{qr}詢問串第一次出現和第i次出現的交*詢問串最後一次出現和第i+1次出現的交\),其中\([ql,qr]\)為\(i\)可能的取值範圍,可以線上段樹上二分得到。
- 記詢問串第\(i\)次出現的左端點和右端點分別為\(L_i\)和\(R_i\)
- 對於詢問串第一次出現和第\(i\)次出現的交,它在大部分的情況下為\(L_{i+1}-L_i\),也即\(R_{i+1}-R_i\),只有在\(i=qr\)時,它有可能為\(R_1-L_i+1\),並且,\(i=qr\)時有可能對應上述的第2種特殊情況。
- 對於詢問串最後一次出現和第\(i+1\)次出現的交,它在大部分情況下為\(R_{i+1}-L_{last}+1\),只有在\(i=ql\)時,有可能對應上述的第1種特殊情況。
- 那麼,我們可以對\(i=ql\),和\(i=qr\)時特殊處理。
- 對於剩下的情況,也即\(i\in (ql,qr)\)時,我們需要求出\(\sum_{ql+1}^{qr-1}(R_{i+1}-R_i)*(R_{i+1}-L_{last}+1)\)。
- 即\(\sum_{ql+1}^{qr-1}(R_{i+1}-R_i)*R_{i+1}-(L_{last}-1)(R_{qr}-R_{ql+1})\)。
- 對於求和部分,是關於兩個相鄰位置的資訊,我們可以線上段樹上額外維護這個資訊。
- 然後這道題就做完了。時間複雜度\(O(QLogN+NLogN)\)。
【程式碼】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; const int MAXP = 200005; const int MAXQ = 300005; const int MAXS = 3e6 + 5; const int MAXLOG = 20; const int MAXC = 10; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct query {int l, r, home; }; struct info {int Min, Max; long long sum; }; info operator + (info a, info b) { info ans = (info) {a.Min, b.Max, a.sum + b.sum}; if (ans.Min == 0) ans.Min = b.Min; if (ans.Max == 0) ans.Max = a.Max; if (a.Max && b.Min) ans.sum += 1ll * b.Min * (b.Min - a.Max); return ans; } struct SegmentTree { struct Node { int lc, rc; info val; } a[MAXS]; int size, n; void init(int x) { n = x; size = 0; } void update(int root) { a[root].val = a[a[root].lc].val + a[a[root].rc].val; } void insert(int &root, int l, int r, int pos) { if (root == 0) root = ++size; if (l == r) { a[root].val = (info) {l, l, 0}; return; } int mid = (l + r) / 2; if (mid >= pos) insert(a[root].lc, l, mid, pos); else insert(a[root].rc, mid + 1, r, pos); update(root); } void insert(int &root, int val) { insert(root, 1, n, val); } int merge(int x, int y) { if (x == 0 || y == 0) return x + y; a[x].lc = merge(a[x].lc, a[y].lc); a[x].rc = merge(a[x].rc, a[y].rc); update(x); return x; } void join(int &x, int y) { x = merge(x, y); } int lower(int root, int l, int r, int val) { if (a[root].val.Min >= val) return a[root].val.Min; int mid = (l + r) / 2; if (a[a[root].lc].val.Max != 0 && a[a[root].lc].val.Max >= val) return lower(a[root].lc, l, mid, val); else return lower(a[root].rc, mid + 1, r, val); } int upper(int root, int l, int r, int val) { if (a[root].val.Max <= val) return a[root].val.Max; int mid = (l + r) / 2; if (a[a[root].rc].val.Min != 0 && a[a[root].rc].val.Min <= val) return upper(a[root].rc, mid + 1, r, val); else return upper(a[root].lc, l, mid, val); } info query(int root, int l, int r, int ql, int qr) { if (ql > qr) return (info) {0, 0, 0}; if (l == ql && r == qr) return a[root].val; int mid = (l + r) / 2; if (mid >= qr) return query(a[root].lc, l, mid, ql, qr); else if (mid + 1 <= ql) return query(a[root].rc, mid + 1, r, ql, qr); else return query(a[root].lc, l, mid, ql, mid) + query(a[root].rc, mid + 1, r, mid + 1, qr); } long long sum(int l, int r) { if (l > r) swap(l, r); return (l + r) * (r - l + 1ll) / 2; } long long query(int root, int l, int r) { if (l == r) return 0; int len = r - l; int r1 = a[root].val.Min, l1 = r1 - len + 1; int rn = a[root].val.Max, ln = rn - len + 1; int ql = lower(root, 1, n, ln); if (ql != r1) ql = upper(root, 1, n, ql - 1); else ql = 0; int qr = upper(root, 1, n, r1 + len - 1); long long ans = 0; if (ql > qr) return 0; if (ql == qr) { if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll); else if (qr == rn) ans += sum(n - r1, n - ln); else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll); } else { info tmp = query(root, 1, n, ql + 1, qr); ans += tmp.sum - (ln - 1ll) * (tmp.Max - tmp.Min); if (ql == 0) ans += (l1 - 2ll) * (r1 - ln + 1ll); else ans += (min(r1, lower(root, 1, n, ql + 1) - len) - (ql - len + 1) + 1ll) * (lower(root, 1, n, ql + 1) - ln + 1ll); if (qr == rn) ans += sum(n - r1, n - ln); else ans += (min(r1, lower(root, 1, n, qr + 1) - len) - (qr - len + 1) + 1ll) * (lower(root, 1, n, qr + 1) - ln + 1ll); } return ans; } } ST; struct SuffixAutomaton { int root, size, last, len; int child[MAXP][MAXC], home[MAXN]; int father[MAXP][MAXLOG], sroot[MAXP]; int fail[MAXP], depth[MAXP], cnt[MAXP]; vector <int> a[MAXP]; vector <query> q[MAXP]; long long ans[MAXQ]; int newnode(int dep) { fail[size] = 0; depth[size] = dep; memset(child[size], 0, sizeof(child[size])); return size++; } void extend(int ch, int from) { int p = last, np = newnode(depth[last] + 1); while (child[p][ch] == 0) { child[p][ch] = np; p = fail[p]; } if (child[p][ch] == np) fail[np] = root; else { int q = child[p][ch]; if (depth[q] == depth[p] + 1) fail[np] = q; else { int nq = newnode(depth[p] + 1); fail[nq] = fail[q]; fail[q] = fail[np] = nq; memcpy(child[nq], child[q], sizeof(child[q])); while (child[p][ch] == q) { child[p][ch] = nq; p = fail[p]; } } } cnt[last = np]++; ST.insert(sroot[np], from); home[from] = np; } void init(char *s) { size = 0; root = last = newnode(0); len = strlen(s + 1); for (int i = 1; i <= len; i++) extend(s[i] - '0', i); for (int i = 1; i < size; i++) { father[i][0] = fail[i]; a[fail[i]].push_back(i); } for (int p = 1; p < MAXLOG; p++) for (int i = 0; i < size; i++) father[i][p] = father[father[i][p - 1]][p - 1]; } void storequery(int l, int r, int from) { int len = r - l + 1, pos = home[r]; for (int i = MAXLOG - 1; i >= 0; i--) if (depth[father[pos][i]] >= len) pos = father[pos][i]; q[pos].push_back((query) {l, r, from}); } void work(int pos) { for (unsigned i = 0; i < a[pos].size(); i++) { work(a[pos][i]); ST.join(sroot[pos], sroot[a[pos][i]]); } for (unsigned i = 0; i < q[pos].size(); i++) ans[q[pos][i].home] = (len - 1ll) * (len - 2ll) / 2 - ST.query(sroot[pos], q[pos][i].l, q[pos][i].r); } void solve(int m) { work(0); for (int i = 1; i <= m; i++) printf("%lld\n", ans[i]); } } SAM; int n, m; char s[MAXN]; int main() { read(n), read(m); ST.init(n); scanf("%s", s + 1); SAM.init(s); for (int i = 1; i <= m; i++) { int l, r; read(l), read(r); SAM.storequery(l, r, i); } SAM.solve(m); return 0; }