Mike and Friends(CodeForces – 547E,AC自动机fail树dfs序上建可持久化线段树)

  • Post author:
  • Post category:其他




一、题目链接


Mike and Friends



二、题目大意





n

n






n





个字符串



s

1

,

s

2

,


,

s

n

s_1,s_2,\cdots,s_n







s










1


















,





s










2


















,











,





s










n

























q

q






q





次询问.

定义函数



f

(

S

,

T

)

f(S,T)






f


(


S


,




T


)





为字符串



S

S






S





在字符串



T

T






T





中的出现次数.

每次询问给出三个整数



l

,

r

,

k

l,r,k






l


,




r


,




k





表示询问



i

=

l

r

f

(

s

i

,

s

k

)

\sum_{i=l}^{r}{f(s_i,s_k)}



















i


=


l










r






















f


(



s










i


















,





s










k


















)






.




1

n

2

×

1

0

5

,

1

q

5

×

1

0

5

,

1

s

i

2

×

1

0

5

,

1

l

r

n

,

1

k

n

1 \leq n \leq 2 \times 10^5, 1 \leq q \leq 5 \times 10^5, 1 \leq \sum{|s_i|} \leq 2 \times 10^5, 1 \leq l \leq r \leq n, 1 \leq k \leq n






1













n













2




×








1



0










5









,




1













q













5




×








1



0










5









,




1























s










i

































2




×








1



0










5









,




1













l













r













n


,




1













k













n





.



三、分析

首先考虑一个弱化问题,假设每次询问



l

=

1

,

r

=

n

l=1,r=n






l




=








1


,




r




=








n





,即只有



k

k






k





在变化. 显然,我们可以进行如下预处理:对



n

n






n





个字符串建立 AC 自动机,在每个字符串的每个前缀对应的字典树节点上



+

1

+1






+


1





. 查询的答案即为



s

k

.

e

n

d

(

)

s_k.end()







s










k


















.


e


n


d


(


)





对应字典树节点的fail子树和. 如上问题可使用AC自动机fail树dfs序上建线段树解决.

对于原问题,与弱化问题相比



l

,

r

l,r






l


,




r





都在变化,只需要将线段树升级成可持久化线段树即可.



四、代码实现

#include <bits/stdc++.h>
using namespace std;

const int M = (int)2e5;
const int N = (int)17;

string s[M + 5];

struct Trie {
	int tot;
	int fail[M + 5], q[M + 5], pos[M + 5];
	int nx[M + 5][26], go[M + 5][26];

	void clear() {
		tot = 0;
		memset(nx[tot], 0, sizeof nx[tot]);
	}

	int newnode() {
		++tot;
		memset(nx[tot], 0, sizeof nx[tot]);
		return tot;
	}

	void insert(int id) {
		int u = 0;
		for(int i = 0, v; i < (int)s[id].size(); ++i) {
			v = s[id][i] - 'a';
			if(nx[u][v] == 0) nx[u][v] = newnode();
			u = nx[u][v];
		}
		pos[id] = u;
	}

	void build() {
		int l = 1, r = 0;
		fail[0] = 0;
		for(int i = 0; i < 26; ++i) if(nx[0][i]) {
			go[0][i] = nx[0][i];
			fail[nx[0][i]] = 0;
			q[++r] = nx[0][i];
		} else go[0][i] = 0;
		while(l <= r) {
			int u = q[l++];
			for(int i = 0; i < 26; ++i) if(nx[u][i]) {
				go[u][i] = nx[u][i];
				fail[nx[u][i]] = go[fail[u]][i];
				q[++r] = nx[u][i];
			} else go[u][i] = go[fail[u]][i];
		}
	}

	vector<int> g[M + 5];
	int st[M + 5], en[M + 5], dfn;
	struct segTree {
		int tot;
		int rt[M + 5];
		int lc[2 * M * (N + 1) + M + 5], rc[2 * M * (N + 1) + M + 5];
		int sum[2 * M * (N + 1) + M + 5];

		void clear() {
			tot = 0;
			lc[tot] = 0, rc[tot] = 0;
		}

		void update(int x, int y, int l, int r, int a, int b) {
			if(l == r) return;
			int mid = (l + r) >> 1;
			if(a <= mid) {
				int lcx = lc[x];
				sum[++tot] = sum[lc[x]] + b;
				lc[y] = tot, rc[y] = rc[x];
				update(lcx, lc[y], l, mid, a, b);
			} else {
				int rcx = rc[x];
				sum[++tot] = sum[rc[x]] + b;
				lc[y] = lc[x], rc[y] = tot;
				update(rcx, rc[y], mid + 1, r, a, b);
			}
		}

		int query(int x, int y, int l, int r, int a, int b) {
			if(a <= l && r <= b) return sum[y] - sum[x];
			int mid = (l + r) >> 1, s = 0;
			if(a <= mid) s += query(lc[x], lc[y], l, mid, a, b);
			if(mid < b)  s += query(rc[x], rc[y], mid + 1, r, a, b);
			return s;
		}
	} tr;

	void dfs(int u) {
		st[u] = ++dfn;
		for(const int &v: g[u]) dfs(v);
		en[u] = dfn;
	}

	void gao(int n) {
		for(int i = 0; i <= tot; ++i) g[i].clear();
		for(int i = 1; i <= tot; ++i) g[fail[i]].push_back(i);
		dfn = 0; dfs(0);
		for(int i = 0; i < n; ++i) {
			tr.rt[i + 1] = ++tr.tot;
			for(int j = 0, u = 0; j < (int)s[i].size(); ++j) {
				u = go[u][s[i][j] - 'a'];
				if(j == 0) tr.update(tr.rt[i], tr.rt[i + 1], 1, tot + 1, st[u], 1);
				else tr.update(tr.rt[i + 1], tr.rt[i + 1], 1, tot + 1, st[u], 1);
			}
		}
	}

	int query(int l, int r, int k) {
		return tr.query(tr.rt[l - 1], tr.rt[r], 1, tot + 1, st[pos[k - 1]], en[pos[k - 1]]);
	}
} trie;

void work() {
	int n, q; cin >> n >> q;
	trie.clear();
	for(int i = 0; i < n; ++i) {
		cin >> s[i];
		trie.insert(i);
	}
	trie.build();
	trie.gao(n);
	for(int i = 0, l, r, k; i < q; ++i) {
		cin >> l >> r >> k;
		cout << trie.query(l, r, k) << "\n";
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	work();
	return 0;
}



版权声明:本文为The___Flash原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。