← 全部文章

CF587F Duff is Mad 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF587F Duff is Mad

题意

  • 给定 nn 个字符串 s1…ns_{1 \dots n}。
  • qq 次询问 sl…rs_{l \dots r} 在 sks_k 中出现了多少次。
  • n,q,∑i=1n∣si∣≤105n,q,\sum_{i=1}^n |s_i| \le 10^5。

题解

一眼看成 CF547E Mike and Friends 然后就再也没想对过。

设 ∑i=1n∣si∣=m\sum_{i=1}^n |s_i| = m。

首先建出 AC 自动机,那么答案可以表示为:

在 fail 树上,将 sl…rs_{l \dots r} 的最后一个节点的子树内所有点的权值 +1+1 后,sks_k 的所有节点的权值和。

考虑离线 + 根号分治,给定一个限制 TT。

若 ∣sk∣>T|s_k| > T,则这样的串最多只有 O(mT)\mathcal O(\frac mT) 个,可以对于每个 sks_k 线性处理。

先将 sks_k 的所有节点的贡献设为 11,然后求出子树贡献和,即每个点的权值 +1+1 后对答案的贡献。

按顺序扫过 nn 个串,每扫到一个串就加上对应的贡献,那么每个询问可以作差得到。

时间复杂度为 O(m2T+qlog⁡q)\mathcal O(\frac {m^2}T + q \log q)。

若 ∣sk∣≤T|s_k| \le T,可以对每个询问 O(∣sk∣)\mathcal O(|s_k|) 处理。

按顺序扫过 nn 个串,每扫到一个串就将其最后一个节点的子树内的所有点的权值 +1+1,处理询问的时候直接暴力求。

这两种操作实际上相当于在 dfs 序上区间加和单点查,树状数组即可。

时间复杂度为 O(nlog⁡m+qTlog⁡m)\mathcal O(n \log m + qT \log m),如果树状数组改成分块的话则可以做到 O(nm+qT)\mathcal O(n \sqrt m + qT)。

那么总时间复杂度为 O(m2T+qlog⁡q+nlog⁡m+qTlog⁡m)\mathcal O(\frac {m^2}T + q \log q + n \log m + qT \log m),取 T=mqlog⁡mT = \frac {m}{\sqrt {q \log m}} 时达到最优复杂度 O(nlog⁡m+qlog⁡q+mqlog⁡m)\mathcal O(n \log m + q \log q + m \sqrt {q \log m})。

代码

const int N = 1e5 + 7;
int n, m, q, T, len[N], S[N], c[N], dfn[N], num;
ll ans[N];
int trie[N][27], fail[N], fa[N], ed[N], tot = 1;
char s[N];
vi e[N];
vector<pi> L[N], R[N], le[N], ri[N];
queue<int> Q;

void dfs1(int x) {
	for (auto y : e[x]) dfs1(y), S[x] += S[y];
}

void dfs2(int x) {
	S[x] = 1, dfn[x] = ++num;
	for (auto y : e[x]) dfs2(y), S[x] += S[y];
}

inline void add(int x, int k) {
	while (x <= num) c[x] += k, x += x & -x;
}

inline int ask(int x) {
	int k = 0;
	while (x) k += c[x], x -= x & -x;
	return k;
}

int main() {
	rd(n), rd(q);
	for (int i = 1; i <= n; i++) {
		rds(s, len[i]), m += len[i];
		int p = 1;
		for (int j = 1; j <= len[i]; j++) {
			int c = s[j] - 'a';
			if (!trie[p][c]) trie[p][c] = ++tot, fa[tot] = p;
			p = trie[p][c];
		}
		ed[i] = p;
	}
	for (int i = 0; i < 26; i++)
		if (trie[1][i]) fail[trie[1][i]] = 1, Q.push(trie[1][i]);
		else trie[1][i] = 1;
	while (Q.size()) {
		int x = Q.front();
		Q.pop();
		for (int i = 0; i < 26; i++)
			if (trie[x][i])
				fail[trie[x][i]] = trie[fail[x]][i], Q.push(trie[x][i]);
			else trie[x][i] = trie[fail[x]][i];
	}
	for (int i = 2; i <= tot; i++) e[fail[i]].pb(i);
	T = m / sqrt(q * log2(m));
	for (int i = 1, l, r, k; i <= q; i++) {
		rd(l), rd(r), rd(k);
		if (len[k] > T) L[k].pb(mp(l, i)), R[k].pb(mp(r, i));
		else le[l].pb(mp(k, i)), ri[r].pb(mp(k, i));
	}
	for (int i = 1; i <= n; i++)
		if (len[i] > T) {
			int p = ed[i];
			while (p != 1) S[p] = 1, p = fa[p];
			dfs1(1);
			sort(L[i].begin(), L[i].end());
			reverse(L[i].begin(), L[i].end());
			sort(R[i].begin(), R[i].end());
			reverse(R[i].begin(), R[i].end());
			ll o = 0;
			for (int j = 1; j <= n; j++) {
				while (L[i].size() && L[i].back().fi == j)
					ans[L[i].back().se] -= o, L[i].pop_back();
				o += S[ed[j]];
				while (R[i].size() && R[i].back().fi == j)
					ans[R[i].back().se] += o, R[i].pop_back();
			}
			for (int i = 2; i <= tot; i++) S[i] = 0;
		}
	dfs2(1);
	for (int i = 1; i <= n; i++) {
		for (pi o : le[i]) {
			int p = ed[o.fi];
			while (p != 1) ans[o.se] -= ask(dfn[p]), p = fa[p];
		}
		add(dfn[ed[i]], 1), add(dfn[ed[i]] + S[ed[i]], -1);
		for (pi o : ri[i]) {
			int p = ed[o.fi];
			while (p != 1) ans[o.se] += ask(dfn[p]), p = fa[p];
		}
	}
	for (int i = 1; i <= q; i++) print(ans[i]);
	return 0;
}

标签:AC 自动机、前缀和/差分、树状数组、根号分治

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…