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

博客详细介绍了如何利用广义后缀自动机(SAM)、线段树合并和树上倍增技术解决Codeforces 666E题目中的字符串查询问题。在给定字符串S和多个字符串T的情况下,针对每个查询,确定S的子串在T中的哪个串出现次数最多,并输出编号和次数。文章通过线段树合并预处理信息,并使用树上倍增找到对应的状态点,实现了O((|S|+∑|Ti|+q)(logm+log(|S|+∑|Ti|)))的时间复杂度解题方案。
摘要由CSDN通过智能技术生成

Address

Meaning

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

Solution

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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值