題目描述node
給一個小寫字母字符串 \(S\) ,\(q\) 次詢問每次給出 \(l,r\) ,求 \(s[l..r]\) 的 Border 。c++
\(1 \leq n,q \leq 10^5\)git
求 Border 等價於在 \([l, r)\) 中找一個點 \(i\) ,知足 \(lcs(i, r) \geq i -l + 1\) ,且 \(i\) 最大。ui
考慮把問題丟到 \(\text{Sam}\) 上,那麼知足的條件變爲 \(len(lca(i,r)) > i - l\) ,對於 \(r\) 的每個祖先算出其做爲 \(lca\) 時最優的 \(i\) ,能夠對 \(\text{parent}\) 樹進行輕重鏈剖分,對於重鏈和輕重鏈切換時候的貢獻分類討論。spa
對於重鏈上的點,其要做爲 \(lca\) 的話 \(i\) 必定是其自己或者一個來自輕子樹裏面的點,因而能夠暴力把每個點所連出去的輕子樹的點的貢獻加在這個點上,考慮一個點被加的次數等同於輕重鏈切換的次數,複雜度沒有問題。code
考慮把重鏈上的點的貢獻並在一塊兒後怎麼求這條重鏈上能獲得的最大的 \(i\) ,將條件移一下項轉變爲 \(i-len(lca(i,r))<l\) ,那麼問題就變爲找出 \(i-len(x) < l\) 且 \(i < r\) 最大的 \(i\) 。其中 \(x\) 是重鏈上的一個點,\(i\) 是其輕子樹裏面的一個點。仔細觀察發現這個東西能夠對於每一條重鏈維護一顆以 \(i\) 爲下標的線段樹,直接在線段樹上二分便可。字符串
考慮輕重鏈切換的狀況,對於跳上去新的重鏈的第一個點 \(x\),其除了以前那條重鏈所在的子樹的點均可以和 \(r\) 產生 \(lca(r, i)=x\) ,這裏直接統計的話複雜度就爆炸了。可是能夠觀察到,對於以前那條重鏈所在的點,必然會在以前的某個點 \(y\) 被算到過,而 \(\text{parent}\) 樹上 \(len\) 是隨深度遞增的,若是在 \(x\) 這裏再被算一次不會產生更優的答案。因此能夠直接統計子樹裏面的全部狀況,用再對每個點開一個線段樹並用線段樹合併維護便可get
實際上一條重鏈並非全部點都會產生貢獻,只有當前跳到的點 \(u\) 以上的點的貢獻才能被算入答案。因此把詢問離線下來拆成若干段重鏈的形式。最後 \(\text{dfs}\) 一遍邊加貢獻邊回答每一段的詢問,最後合併每一段的答案求出最終的結果。總複雜度 \(O(nlog^2n)\)it
/*program by mangoyang*/ #include<bits/stdc++.h> #define inf (0x7f7f7f7f) #define Max(a, b) ((a) > (b) ? (a) : (b)) #define Min(a, b) ((a) < (b) ? (a) : (b)) typedef long long ll; using namespace std; template <class T> inline void read(T &x){ int ch = 0, f = 0; x = 0; for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1; for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48; if(f) x = -x; } #define mk make_pair #define fi first #define se second const int N = 500005; char s[N]; int pos[N], Ans[N], m; struct Node{ int id, l, r; }; vector<Node> q[N]; struct SegmentTree{ int val[N*25], lc[N*25], rc[N*25], rt[N*10], size; inline SegmentTree(){ memset(val, 127, sizeof(val)); } inline void ins(int &u, int l, int r, int pos, int x){ if(!u) u = ++size; if(l == r) return (void) (val[u] = min(val[u], x)); int mid = l + r >> 1; if(pos <= mid) ins(lc[u], l, mid, pos, x); else ins(rc[u], mid + 1, r, pos, x); val[u] = min(val[lc[u]], val[rc[u]]); } inline int query(int u, int l, int r, int pos, int lim){ if(!u) return 0; if(l == r) return val[u] < lim ? l : 0; int mid = l + r >> 1, res = 0; if(mid + 1 >= pos) return query(lc[u], l, mid, pos, lim); if(val[rc[u]] < lim) res = query(rc[u], mid + 1, r, pos, lim); if(!res) return query(lc[u], l, mid, pos, lim); else return res; } inline int merge(int x, int y, int l, int r){ if(!x || !y) return x + y; int o = ++size, mid = l + r >> 1; if(l == r) val[o] = min(val[x], val[y]); else{ lc[o] = merge(lc[x], lc[y], l, mid); rc[o] = merge(rc[x], rc[y], mid + 1, r); val[o] = min(val[lc[o]], val[rc[o]]); } return o; } }T1, T2; namespace Sam{ vector<int> g[N]; vector< pair<int, int> > s[N]; int sz[N], top[N], ms[N]; int ch[N][26], len[N], fa[N], id[N], size = 1, tail = 1; inline int newnode(int x){ return len[++size] = x, size; } inline void ins(int c, int x){ int p = tail, np = newnode(len[p] + 1); pos[x] = np, id[np] = x; for(; p && !ch[p][c]; p = fa[p]) ch[p][c] = np; if(!p) return (void) (fa[np] = 1, tail = np); int q = ch[p][c]; if(len[q] == len[p] + 1) fa[np] = q; else{ int nq = newnode(len[p] + 1); fa[nq] = fa[q], fa[q] = fa[np] = nq; for(int i = 0; i < 26; i++) ch[nq][i] = ch[q][i]; for(; p && ch[p][c] == q; p = fa[p]) ch[p][c] = nq; }tail = np; } inline void addedge(){ for(int i = 2; i <= size; i++) g[fa[i]].push_back(i); } inline void Buildtree(int u){ if(id[u]) T1.ins(T1.rt[u], 1, size, id[u], id[u]); sz[u] = 1; for(int i = 0; i < (int) g[u].size(); i++){ int v = g[u][i]; Buildtree(v); T1.rt[u] = T1.merge(T1.rt[u], T1.rt[v], 1, size); sz[u] += sz[v]; if(sz[v] > sz[ms[u]]) ms[u] = v; } } inline void prework(int u, int x){ if(id[u]) s[x].push_back(mk(id[u], id[u] - len[x])); for(int i = 0; i < (int) g[u].size(); i++) prework(g[u][i], x); } inline void dfs(int u, int chain){ top[u] = chain; if(id[u]) s[u].push_back(mk(id[u], id[u] - len[u])); if(ms[u]) dfs(ms[u], chain); for(int i = 0; i < (int) g[u].size(); i++){ int v = g[u][i]; if(v != ms[u]) dfs(v, v), prework(v, u); } } inline void Doit(){ addedge(), Buildtree(1), dfs(1, 1); } inline int query(int u, int l, int r, Node Query){ int res = T1.query(T1.rt[u], 1, size, r, l + len[u]); while(u){ q[u].push_back(Query), u = fa[top[u]]; if(!u) break; res = max(res, T1.query(T1.rt[u], 1, size, r, l + len[u])); } return res < l ? 0 : res - l + 1; } inline void solve(int u){ for(int i = 0; i < (int) s[u].size(); i++) T2.ins(T2.rt[top[u]], 1, size, s[u][i].fi, s[u][i].se); for(int i = 0; i < (int) q[u].size(); i++){ int l = q[u][i].l, r = q[u][i].r; int now = T2.query(T2.rt[top[u]], 1, size, r, l); Ans[q[u][i].id] = max(Ans[q[u][i].id], (now < l ? 0 : now - l + 1)); } for(int i = 0; i < (int) g[u].size(); i++) solve(g[u][i]); } } int main(){ scanf("%s", s + 1); int len = strlen(s + 1); for(int i = 1; i <= len; i++) Sam::ins(s[i] - 'a', i); Sam::Doit(); read(m); for(int i = 1, l, r; i <= m; i++){ read(l), read(r); Ans[i] = Sam::query(pos[r], l, r, (Node){i, l, r}); } Sam::solve(1); for(int i = 1; i <= m; i++) printf("%d\n", Ans[i]); return 0; }