[Codeforces 666E]Forensic Examination(广义后缀自动机 + 线段树合并 + 树上倍增)

Address

Meaning

  • 给定一个字符串 S S m m 个字符串 T 1 , T 2 , . . . , T m T_1,T_2,...,T_m
  • q q 个询问,每个询问给出四个参数 l l r r p l p_l p r p_r
  • S S 的子串 [ p l , p r ] [p_l,p_r] T l T_l T l + 1 T_{l+1} 、…、 T r T_r 中的哪个串中出现的次数最多
  • 如果出现次数最多的有多个串则取编号最小的
  • 对于每组询问输出编号和出现次数
  • 1 S 5 × 1 0 5 1\le|S|\le5\times10^5 1 m 5 × 1 0 4 1\le m\le5\times10^4 1 i = 1 m T i 5 × 1 0 4 1\le\sum_{i=1}^m|T_i|\le5\times10^4 1 q 5 × 1 0 5 1\le q\le5\times10^5
  • 时限 6s ,空限 768MB

Solution

  • 首先把所有的 T 1 , T 2 , . . . , T m T_1,T_2,...,T_m S S 放在一起建立广义 SAM ,下面就对这个 SAM 对应的 Parent 树进行讨论
  • 下面我们定义「黑点」为对 R i g h t Right 集合有贡献的点(即 SAM 构建过程中,不是被拆解出的所有状态点)
  • 第一个问题:对于树上的已知节点 u u 以及已知区间 [ l , r ] [l,r] ,如何知道 T [ l . . . r ] T[l...r] 这些串中,哪个串出现状态 u u 的次数最多(相同者取最小编号),以及出现次数
  • 根据 Parent 树的性质,状态 u u R i g h t Right 集合可以用 u u 的子树内所有黑点的某些东西表示出来
  • 又根据 SAM 的性质,一个黑点对应原串的一个前缀。相应地,在广义 SAM 上,一个黑点对应原串集合的 Trie 树上根到一个点的路径
  • 可以在每个黑点上,用一个vector储存这个黑点对应了字符串集合 T T 中哪些串的前缀
  • 以下把一个黑点上的 vector 内存的 T T 内的字符串编号记作 [ 1 , m ] [1,m] 内的某种颜色
  • 我们的做法出来了:这个问题就是求 u u 的子树内哪种颜色出现次数最多(相同则取最小编号颜色)以及出现次数
  • 可以使用线段树合并或者可持久化线段树解决这个问题
  • 对每个点开一棵线段树,下标为颜色,每个节点存出现次数最多的颜色及出现次数,对于每个点 u u ,通过线段树合并从 u u 的子节点的信息合并到点 u u 的信息
  • 到现在,我们已经解决了第一个问题
  • 第二个问题:已知区间 [ p l , p r ] [p_l,p_r] ,如何找到子串 S [ p l . . . p r ] S[p_l...p_r] 在 SAM 上对应的状态点
  • 如果是 S [ 1... p r ] S[1...p_r] 对应的状态点,那么要找的点显然是 S S 的长度为 p r p_r 的前缀对应的黑点
  • 而根据 Parent 树的性质, S [ p l . . . p r ] S[p_l...p_r] 对应的点是 S [ 1... p r ] S[1...p_r] 对应点的祖先,且每个点的父亲节点的 m a x l maxl 都严格小于自己的 m a x l maxl
  • 于是,如果 S [ 1... p r ] S[1...p_r] 对应的状态点为 u u ,那么我们要做的就是找到 u u 的祖先中,离 u u 最远的,满足 m a x l v r l + 1 maxl_v\ge r-l+1 的点 v v
  • 可以使用树上倍增找到这个点 v v
  • 这样我们的做法就出来了:先通过线段树合并预处理 Parent 树每个点的子树内信息,询问时通过树上倍增找到 S [ p l . . . p r ] S[p_l...p_r] 对应的状态 u u ,再查询状态 u u 对应的线段树上区间 [ l , r ] [l,r] 内的信息
  • 复杂度 O ( ( S + i = 1 m T i + q ) ( log m + log ( S + i = 1 m T i ) ) ) O((|S|+\sum_{i=1}^m|T_i|+q)(\log m+\log(|S|+\sum_{i=1}^m|T_i|)))

Code

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

const int N = 55e4 + 5, M = 11e5 + 5, L = 1e7 + 5, LogN = 22;

char s[N];
int m, q, trie[N][26], top[N], totTrie, totSam, totTree, ends[N],
ecnt, nxt[M], adj[M], go[M], rt[M], fa[M][LogN];

std::vector<int> col[M];

void add_edge(int u, int v)
{
	nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}

struct SAM
{
	int fa, maxl, go[26];
} sam[M];

struct data
{
	int col, maxv;
	
	friend inline bool operator > (data a, data b)
	{
		return a.maxv > b.maxv || (a.maxv == b.maxv && a.col < b.col);
	}
};

struct SegTree
{
	int lc, rc; data val;
} T[L];

int extend(int c, int lst)
{
	int x = lst;
	sam[lst = ++totSam].maxl = sam[x].maxl + 1;
	for (; x && !sam[x].go[c]; x = sam[x].fa)
		sam[x].go[c] = lst;
	if (!x) return sam[lst].fa = 1, lst;
	int y = sam[x].go[c];
	if (sam[x].maxl + 1 == sam[y].maxl)
		return sam[lst].fa = y, lst;
	int p; sam[p = ++totSam] = sam[y];
	sam[lst].fa = sam[y].fa = p;
	sam[p].maxl = sam[x].maxl + 1;
	for (; x && sam[x].go[c] == y; x = sam[x].fa)
		sam[x].go[c] = p;
	return lst;
}

void ins(int x)
{
	int i, n = strlen(s + 1), u = 1;
	for (int i = 1; i <= n; i++)
	{
		int c = s[i] - 'a';
		if (!trie[u][c])
			top[trie[u][c] = ++totTrie] = extend(c, top[u]);
		u = trie[u][c];
		if (x) col[top[u]].push_back(x);
		else ends[i] = top[u];
	}
}

void add(int l, int r, int pos, int &p)
{
	if (!p) p = ++totTree;
	if (l == r) return (void) (T[p].val.col = l, T[p].val.maxv++);
	int mid = l + r >> 1;
	if (pos <= mid) add(l, mid, pos, T[p].lc);
	else add(mid + 1, r, pos, T[p].rc);
	if (T[p].lc && T[p].rc) T[p].val = Max(T[T[p].lc].val, T[T[p].rc].val);
	else T[p].val = T[p].lc ? T[T[p].lc].val : T[T[p].rc].val;
}

data query(int l, int r, int s, int e, int p)
{
	if (!p) return (data) {s, 0};
	if (l == s && r == e) return T[p].val;
	int mid = l + r >> 1;
	if (e <= mid) return query(l, mid, s, e, T[p].lc);
	else if (s >= mid + 1) return query(mid + 1, r, s, e, T[p].rc);
	else return Max(query(l, mid, s, mid, T[p].lc),
		query(mid + 1, r, mid + 1, e, T[p].rc));
}

int merge_tree(int l, int r, int x, int y)
{
	if (!x || !y) return x ^ y;
	if (l == r) return T[++totTree].val.maxv = T[x].val.maxv + T[y].val.maxv,
		T[totTree].val.col = l, totTree;
	int mid = l + r >> 1, p = ++totTree;
	T[p].lc = merge_tree(l, mid, T[x].lc, T[y].lc);
	T[p].rc = merge_tree(mid + 1, r, T[x].rc, T[y].rc);
	if (T[p].lc && T[p].rc) T[p].val = Max(T[T[p].lc].val, T[T[p].rc].val);
	else T[p].val = T[p].lc ? T[T[p].lc].val : T[T[p].rc].val;
	return p;
}

void dfs(int u)
{
	int sz = col[u].size();
	for (int i = 0; i < sz; i++)
		add(1, m, col[u][i], rt[u]);
	for (int i = 0; i < 20; i++)
		fa[u][i + 1] = fa[fa[u][i]][i];
	for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
		fa[v][0] = u, dfs(v), rt[u] = merge_tree(1, m, rt[u], rt[v]);
}

int get_substr(int l, int r)
{
	int u = ends[r];
	for (int i = 20; i >= 0; i--)
		if (sam[fa[u][i]].maxl >= r - l + 1)
			u = fa[u][i];
	return u;
}

int main()
{
	int pl, pr, l, r;
	top[totTrie = totSam = 1] = 1;
	scanf("%s", s + 1);
	ins(0);
	m = read();
	for (int i = 1; i <= m; i++)
		scanf("%s", s + 1), ins(i);
	for (int i = 2; i <= totSam; i++)
		add_edge(sam[i].fa, i);
	q = read();
	dfs(1);
	while (q--)
	{
		l = read(); r = read(); pl = read(); pr = read();
		data res = query(1, m, l, r, rt[get_substr(pl, pr)]);
		printf("%d %d\n", res.col, res.maxv);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/85720195
今日推荐