首页 > 技术文章 > 「BJWC2018」Border 的四种求法

mangoyang 2018-10-15 21:10 原文

「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;
}

推荐阅读