[COCI2015]Divljak

题目大意

n n n 个字符串 S 1 , S 2 , ⋯   , S n S_1, S_2, \cdots, S_n S1,S2,,Sn 和一个字符串集合 T T T,一开始 T T T 为空。

然后有两个操作:

  1. 1 P T T T 里加一个字符串 P P P
  2. 2 x 询问集合 T T T 中有多少个字符串包含串 S x S_x Sx (我们称串 A A A 包含串 B B B,当且仅当 B B B A A A 的子串)。

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

解题思路

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

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

首先考虑对 S 1 , S 2 , ⋯   , S n S_1, S_2, \cdots, S_n S1,S2,,SnAC 自动机。

然后就没思路了。。。

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

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

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

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

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

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

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

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

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

树上差分就有了。

对于每个询问,对串在 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值