[COCI2015]Divljak

题目大意

nn 个字符串 S1,S2,,SnS_1, S_2, \cdots, S_n 和一个字符串集合 TT,一开始 TT 为空。

然后有两个操作:

  1. 1 PTT 里加一个字符串 PP
  2. 2 x 询问集合 TT 中有多少个字符串包含串 SxS_x (我们称串 AA 包含串 BB,当且仅当 BBAA 的子串)。

对于 100%100\% 的数据,1n,q1051 \leq n,q \leq 10^5,字符串由小写字母构成,所有字符串的总长 2×106\le 2 \times 10^6

解题思路

前置知识:AC 自动机树链剖分,树上差分的思想。

显然,看到数据范围可以想到肯定不能暴力。

首先考虑对 S1,S2,,SnS_1, S_2, \cdots, S_nAC 自动机。

然后就没思路了。。。

但是,往往模拟一遍就会有思路了。

于是,模拟暴力时是怎样对答案进行求和的。

显然,做过模板题的都知道,将串放在 AC 自动机上一位一位跑,每跑一位将 fail 链上的所有贡献加起来,等等,这是不是似曾相识??

考虑建 fail 树,fail 链即是一个点到根节点的简单路径。

现在就要用到树链剖分了。

那树上差分的身影又在哪呢?

可以想到,对串的每一位都求路径和,肯定会炸。

考虑优化,将串的每一位根据 dfn 序进行排序。

然后,将每个点贡献加 11,相邻两点的 lca 贡献减 11

树上差分就有了。

对于每个询问,对串在 trie 上结束的节点求一遍子树和就行了。

AC CODE

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

const int _ = 2e6 + 7;

int n, m;

int cnt = 1, tr[_][27], fail[_], tag[_];

char c[_];

void insert(char *s, int id)
{
	int p = 1;
	for(int i = 1, len = strlen(s + 1); i <= len; ++i)
	{
		int v = s[i] - 'a';
		if(!tr[p][v]) tr[p][v] = ++cnt;
		p = tr[p][v];
	}
	tag[id] = p;
}

void getfail()
{
	for (int i = 0; i < 26; i ++)
		tr[0][i] = 1;
	queue<int> q;
	q.push(1);
	fail[1] = 0;
	while(!q.empty())
	{
		int now = q.front();
		q.pop();
		for(int i = 0; i < 26; ++i)
		{
			if(tr[now][i])
			{
				q.push(tr[now][i]);
				fail[tr[now][i]] = tr[fail[now]][i];
			}
			else
			{
				tr[now][i] = tr[fail[now]][i];
			}
		}
	}
}

int tot, head[_], to[_ << 1], nxt[_ << 1];

void add(int u, int v)
{
	to[++tot] = v;
	nxt[tot] = head[u];
	head[u] = tot;
}

int cnt_node, dep[_], siz[_], hson[_], top[_], fa[_], dfn[_];

void dfs1(int u, int d = 1)
{
	dep[u] = d;
	siz[u] = 1;
	for(int i = head[u]; i; i = nxt[i])
	{
		int v = to[i];
		if(dep[v]) continue;
		fa[v] = u;
		dfs1(v, d + 1);
		siz[u] += siz[v];
		if(siz[v] > siz[hson[u]]) hson[u] = v;
	}
}

void dfs2(int u, int topf)
{
	dfn[u] = ++cnt_node;
	top[u] = topf;
	if(!hson[u]) return;
	dfs2(hson[u], topf);
	for(int i = head[u]; i; i = nxt[i])
	{
		int v = to[i];
		if(top[v]) continue;
		dfs2(v, v);
	}
}

int LCA(int u, int v)
{
	while(top[u] != top[v])
	{
		if(dep[top[u]] < dep[top[v]]) swap(u, v);
		u = fa[top[u]];
	}
	return dep[u] > dep[v] ? v : u;
}

int k[_];

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

void update(int x, int val)
{
	for(int i = x; i <= cnt; i += lowbit(i))
	{
		k[i] += val;
	}
}

int query(int x)
{
	int res = 0;
	for(int i = x; i; i -= lowbit(i))
	{
		res += k[i];
	}
	return res;
}

bool cmp(int x, int y)
{
	return dfn[x] < dfn[y];
}

int q[_];

signed main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i)
	{
		scanf("%s", c + 1);
		insert(c, i);
	}
	getfail();
	for(int i = 2; i <= cnt; ++i)
		add(fail[i], i);
	dfs1(1);
	dfs2(1, 1);
	scanf("%d", &m);
	for(int i = 1; i <= m; ++i)
	{	
		int op, kkk;
		scanf("%d", &op);
		if(op == 1)
		{
			scanf("%s", c + 1);
			int len = strlen(c + 1);
			for(int j = 1, p = 1; j <= len; ++j)
			{
				int v = c[j] - 'a';
				p = tr[p][v];
				q[j] = p;
			}
			sort(q + 1, q + len + 1, cmp);
			for(int j = 1; j <= len; ++j)
				update(dfn[q[j]], 1);
			for(int j = 1; j < len; ++j) 
				update(dfn[LCA(q[j], q[j + 1])], -1);
		}
		else
		{
			scanf("%d", &kkk);
			int x = tag[kkk];
			printf("%d\n", query(dfn[x] + siz[x] - 1) - query(dfn[x] - 1));
		}
	}
	return 0;
}
posted @ 2021-09-24 19:44  蒟蒻orz  阅读(2)  评论(0编辑  收藏  举报  来源