「BJWC2018」Border 的四種求法
題目描述
給一個小寫字母字符串 \(S\) ,\(q\) 次詢問每次給出 \(l,r\) ,求 \(s[l..r]\) 的 Border 。
\(1 \leq n,q \leq 10^5\)
解題思路 :
求 Border 等價於在 \([l, r)\) 中找一個點 \(i\) ,滿足 \(lcs(i, r) \geq i -l + 1\) ,且 \(i\) 最大。
考慮把問題丟到 \(\text{Sam}\) 上,那么滿足的條件變為 \(len(lca(i,r)) > i - l\) ,對於 \(r\) 的每一個祖先算出其作為 \(lca\) 時最優的 \(i\) ,可以對 \(\text{parent}\) 樹進行輕重鏈剖分,對於重鏈和輕重鏈切換時候的貢獻分類討論。
對於重鏈上的點,其要作為 \(lca\) 的話 \(i\) 一定是其本身或者一個來自輕子樹里面的點,於是可以暴力把每一個點所連出去的輕子樹的點的貢獻加在這個點上,考慮一個點被加的次數等同於輕重鏈切換的次數,復雜度沒有問題。
考慮把重鏈上的點的貢獻並在一起后怎么求這條重鏈上能得到的最大的 \(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\) 這里再被算一次不會產生更優的答案。所以可以直接統計子樹里面的所有情況,用再對每一個點開一個線段樹並用線段樹合並維護即可
實際上一條重鏈並不是所有點都會產生貢獻,只有當前跳到的點 \(u\) 以上的點的貢獻才能被算入答案。所以把詢問離線下來拆成若干段重鏈的形式。最后 \(\text{dfs}\) 一遍邊加貢獻邊回答每一段的詢問,最后合並每一段的答案求出最終的結果。總復雜度 \(O(nlog^2n)\)
/*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;
}