Description

传送门


Solution

首先看到这题就想SAM对吧,然后qwaszx写了一发常数太大过不了就果断改AC自动机对吧。

考虑对\(S\)建立\(AC\)自动机,因为字符串的所有前缀的所有后缀是字符串的所有子串,而\(fail\)指针指的状态就是该状态的最长可识别后缀,所以在对\(S\)建好\(AC\)自动机之后就把插入的字符串扔到\(AC\)自动机上跑,它能到达的所有状态就是所有的前缀。这样再对每个状态跳所有的\(fail\)指针就能把该字符串的所有可识别子串在\(AC\)自动机上找出来了。

所以每次新插入字符串的时候只要在\(fail\)树上把所有经过的状态到\(root\)的链都加一,查询的时候查该字符串在\(AC\)自动机上的位置的值就行了。

直接这样做不大好做,所以考虑树上差分,先把所有的经过的状态都找出来,然后按\(dfs\)序排序,每次对每个状态单点加,对相邻状态的\(lca\)单点减。最后查询某个点的时候只需要查询子树和即可。

考虑为什么按照\(dfs\)序排序,原来的这些点是杂乱无章的,在按\(dfs\)序排序后就会按照从左往右的顺序依次修改,这样可以保证不重不漏。如果学过虚树的话应该很容易就能理解。


Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm> 
#include <queue> 

using namespace std;

const int N = 100000;
const int M = 2000000;

int head[M + 50], num, id[N + 50], tree[M + 50], trie[M + 50][26], cnt, root, top[M + 50], maxson[M + 50], siz[M + 50], dfx[M + 50], dfxx, f[M + 50], dep[M + 50], n, tmp[M + 50], len, fail[M + 50]; 

char st[M + 50];

struct Node
{
	int next, to;
} edge[M + 50];

void Addedge(int u, int v)
{
	edge[++num] = (Node){head[u], v};
	head[u] = num;
	return;
}

void Insert(char st[M + 50], int bh)
{
	int l = strlen(st + 1), now = root;
	for (int i = 1; i <= l; i++)
	{
		int c = st[i] - 'a';
		if (!trie[now][c]) trie[now][c] = ++cnt;
		now = trie[now][c]; 	
	} 
	id[bh] = now;
	return;
}

void Build_AC_auto()
{
	queue<int> q;
	for (int i = 0; i <= 25; i++) if (trie[root][i]) q.push(trie[root][i]);
	while (!q.empty())	
	{
		int u = q.front(); q.pop();
		for (int i = 0; i <= 25; i++)
			if (trie[u][i]) fail[trie[u][i]] = trie[fail[u]][i], q.push(trie[u][i]);
			else trie[u][i] = trie[fail[u]][i];
	}
	return;
}

void Build_Fail_tree()
{
	for (int i = 1; i <= cnt; i++) Addedge(fail[i], i);
	return;
}

void Dfs1(int x)
{
	dfx[x] = ++dfxx; siz[x] = 1;	
	int maxx = 0;
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to; f[v] = x; dep[v] = dep[x] + 1;
		Dfs1(v);
		siz[x] += siz[v];
		if (siz[v] > maxx) maxx = siz[v], maxson[x] = v;
	}
	return;
} 

void Dfs2(int x, int topf)
{
	top[x] = topf;
	if (!maxson[x]) return;
	Dfs2(maxson[x], topf);
	for (int i = head[x]; i; i = edge[i].next)
	{
		int v = edge[i].to;
		if (v == maxson[x]) continue;
		Dfs2(v, v);
	} 
	return;
 } 

int Lca(int a, int b)
{
	while (top[a] != top[b])
	{
	//	cout << a << " " << b << endl;
		if (dep[top[a]] < dep[top[b]]) swap(a, b);
		a = f[top[a]];	
	}
	return dep[a] < dep[b] ? a : b; 
}

int Lowbit(int x)
{
	return x & (-x);
}

void Add(int pos, int val)
{
	for (int i = pos; i <= cnt + 1; i += Lowbit(i)) tree[i] += val;
	return;
}

int Query(int pos)
{
	int ans = 0;
	for (int i = pos; i; i -= Lowbit(i)) ans += tree[i];
	return ans;
}

int Cmp(int a, int b)
{
	return dfx[a] < dfx[b];
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%s", st + 1), Insert(st, i);
	Build_AC_auto(); Build_Fail_tree(); dep[0] = 1; Dfs1(0); Dfs2(0, 0);
	int q;
	scanf("%d", &q); 
	int opt, ask;
	while (q--)
	{
		scanf("%d", &opt); 
		if (opt == 1)
		{
			scanf("%s", st + 1); len = 0; 
			int now = root, l = strlen(st + 1);
			for (int i = 1; i <= l; i++)
			{
				int c = st[i] - 'a';
				now = trie[now][c];
				tmp[++len] = now;
			}
			sort(tmp + 1, tmp + len + 1, Cmp);
			len = unique(tmp + 1, tmp + len + 1) - tmp - 1;
			for (int i = 1; i <= len; i++)
			{
				Add(dfx[tmp[i]], 1);
				if (i > 1) Add(dfx[Lca(tmp[i], tmp[i - 1])], -1);
			} 
		}
		else
		{
			scanf("%d", &ask);
			printf("%d\n", Query(dfx[id[ask]] + siz[id[ask]] - 1) - Query(dfx[id[ask]] - 1));
		}
	}
	return 0;
}