[BZOJ 2555]SubString

本文介绍了一种利用LCT树动态维护后缀自动机的方法,以解决字符串匹配问题。通过对后缀自动机的right集合大小进行维护,实现字符串插入及查询连续子串出现次数的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

BZOJ传送门

题目描述

懒得写背景了,给你一个字符串init,要求你支持两个操作

(1):在当前字符串的后面插入一个字符串

(2):询问字符串 s s s在当前字符串中出现了几次?(作为连续子串)

你必须在线支持这些操作。

输入输出格式

输入格式:

两行,两个字符串 s 1 s_1 s1 s 2 s_2 s2,长度分别为 n 1 n_1 n1 n 2 n_2 n2 1 ≤ n 1 , n 2 ≤ 200000 1 \le n_1, n_2\le 200000 1n1,n2200000,字符串中只有小写字母

输出格式:

输出一个整数表示答案

输入输出样例

输入样例#1:
2
A
QUERY B
ADD BBABBBBAAB
输出样例#1:
0

解题分析

这道题就是要我们动态维护后缀自动机的 r i g h t right right集合大小, 显然每次构造后暴力向上跳是不科学的, 而我们发现添加一个字符等于在 p a r e n t parent parent树上面到根节点的路径上整体加 1 1 1。 又因为涉及到 p a r e n t parent parent链的断开、动态连接操作, 我们考虑用 L C T LCT LCT维护 p a r e n t parent parent树,在我们更改 p a r e n t parent parent数组的时候 l i n k link link c u t cut cut

因为是有根树, 所以不能 s p l i t split split, 我们区间加直接 a c c e s s access access后再 s p l a y splay splay, 进而在 p u s h d o w n pushdown pushdown的时候将标记下传。

还有一个坑点:一定要单独开一个数组保存每个节点的 p a r e n t parent parent!因为 L C T LCT LCT形态不固定, 会把真正的 p a r e n t parent parent s p l a y splay splay到奇奇怪怪的地方。

总复杂度 O ( N l o g ( N ) ) O(Nlog(N)) O(Nlog(N)) 600000 600000 600000 L C T LCT LCT跑的还贼快?

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 1500050
char dat[MX], opt[10];
int l, cnt = 1, top, cur, last = 1, root = 1, key, lastans, sta[MX], fat[MX];
struct Node {int son[2], fat, tag, to[26], len, siz;} tree[MX];
namespace LCT
{
	#define ls tree[now].son[0]
	#define rs tree[now].son[1]
	#define dad tree[now].fat
	IN bool get(R int now) {return tree[dad].son[1] == now;}
	IN bool nroot(R int now) {return tree[dad].son[0] == now || tree[dad].son[1] == now;}
	IN void add(R int now, R int val) {if(now) tree[now].siz += val, tree[now].tag += val;}
	IN void pushdown(R int now)
	{if(tree[now].tag) add(ls, tree[now].tag), add(rs, tree[now].tag), tree[now].tag = 0;}
	IN void rotate(const int &now)
	{
		R bool dir = get(now);
		R int fa = dad, grand = tree[fa].fat;
		tree[fa].son[dir] = tree[now].son[dir ^ 1];
		tree[tree[now].son[dir ^ 1]].fat = fa;
		if(nroot(fa)) tree[grand].son[get(fa)] = now;
		tree[now].fat = grand;
		tree[now].son[dir ^ 1] = fa;
		tree[fa].fat = now;
	}
	IN void splay(R int now)
	{
		int tmp = now, fa;
		sta[top = 1] = now;
		W (nroot(now)) sta[++top] = now = dad;
		W (top) pushdown(sta[top--]);
		now = tmp;
		W (nroot(now))
		{
			fa = dad;
			if(nroot(fa)) rotate(get(fa) == get(now) ? fa : now);
			rotate(now);
		}
	}
	IN void access(R int now)
	{
		for (R int x = 0; now; x = now, now = dad)
		splay(now), rs = x;
	}
	IN void link(R int x, R int y)
	{
		tree[x].fat = y;
		access(y), splay(y);
		add(y, tree[x].siz);
	}
	IN void cut(R int now)
	{
		access(now); splay(now);
		add(ls, -tree[now].siz);
		ls = tree[ls].fat = 0;
	}
}
namespace SAM
{
	IN void insert(R int id)
	{
		R int now = last, tar;
		cur = ++cnt; tree[cur].siz = 1; tree[cur].len = tree[last].len + 1; last = cur;
		for (; now && !tree[now].to[id]; now = fat[now])
		tree[now].to[id] = cur;
		if(!now) return fat[cur] = root, LCT::link(cur, root), void(); tar = tree[now].to[id];
		if(tree[tar].len == tree[now].len + 1) return fat[cur] = tar, LCT::link(cur, tar), void();
		int nw = ++cnt; tree[nw].len = tree[now].len + 1;
		std::memcpy(tree[nw].to, tree[tar].to, sizeof(tree[nw].to));
		fat[nw] = fat[tar], LCT::link(nw, fat[tar]); LCT::cut(tar);
		fat[cur] = nw, LCT::link(cur, nw); fat[tar] = nw, LCT::link(tar, nw);
		for (; now && tree[now].to[id] == tar; now = fat[now]) tree[now].to[id] = nw;
	}
	IN void init(R int key)
	{
		for (R int i = 0; i < l; ++i)
		{
			key = (key * 131 + i) % l;
			std::swap(dat[i], dat[key]);
		}
	}
}
int main(void)
{
	int T;
	scanf("%d%s", &T, dat);
	l = std::strlen(dat);
	for (R int i = 0; i < l; ++i) SAM::insert(dat[i] - 'A');
	W (T--)
	{
		scanf("%s%s", opt, dat);
		l = std::strlen(dat);
		SAM::init(key);
		if(opt[0] == 'A')
		for (R int i = 0; i < l; ++i)
		SAM::insert(dat[i] - 'A');
		else
		{
			R int now = 1;
			for (R int i = 0; i < l; ++i) now = tree[now].to[dat[i] - 'A'];
			if(!now) puts("0");
			else
			{
				LCT::access(now);
				LCT::splay(now);
				printf("%d\n", lastans = tree[now].siz);
				key ^= lastans;
			}
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值