[Luogu P4770] [BZOJ 5417] [UOJ 395] [NOI2018]你的名字

洛谷传送门

BZOJ传送门

UOJ传送门

题目背景

实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题。

题目描述

小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了。

由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手册规定:每年由命题委员会规定一个小写字母字符串,我们称之为那一年的命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。

由于一些特殊的原因,小A 不知道ION2017 每道题的名字,但是他通过一些特殊手段得到了ION2017 的命名串,现在小A 有Q 次询问:每次给定ION2017 的命名串和ION2018 的命名串,求有几种题目的命名,使得这个名字一定满足命题委员会的规定,即是ION2018 的命名串的一个非空连续子串且一定不会和ION2017 的任何一道题目的名字相同。

由于一些特殊原因,所有询问给出的ION2017 的命名串都是某个串的连续子串,详细可见输入格式。

输入输出格式

输入格式:

第一行一个字符串 S S S ,之后询问给出的ION2017 的命名串都是 S S S 的连续子串。 第二行一个正整数 Q Q Q,表示询问次数。 接下来 Q Q Q 行,每行有一个字符串 T T T 和两个正整数 l , r l,r l,r,表示询问如果ION2017 的 命名串是 S [ l . . r ] S [l..r] S[l..r],ION2018 的命名串是 T T T 的话,有几种命名方式一定满足规定。

输出格式:

输出 Q Q Q 行,第 i i i 行一个非负整数表示第i 个询问的答案。

输入输出样例

输入样例#1:

scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9

输出样例#1:

12
10
4

说明

| 测试点 | $∣S∣\leq $ | Q ≤ Q\leq Q | ∑ ∣ T ∣ ≤ \sum |T|\leq T | 其他限制 |
| :----: | :-------------: | :-----: | :-------------: | :-----------: |
| 1 | 200 200 200 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 T200 |
| 2 | 1000 1000 1000 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 T200 |
| 3 | 1000 1000 1000 | 200 200 200 | 40000 40000 40000 | ∣ T ∣ ≤ 200 |T|\leq 200 T200 |
| 4 | 1000 1000 1000 | 200 200 200 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 5 | 1000 1000 1000 | 200 200 200 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 6 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 1 1 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 7 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 1 1 | 5 × 1 0 5 5 \times 10^5 5×105 | 无 |
| 8 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 2 × 1 0 5 2 \times 10^5 2×105 | 无 |
| 9 | 1 0 5 10^5 105 | 1 0 5 10^5 105 | 2 × 1 0 5 2 \times 10^5 2×105 | 字符串随机 |
| 10 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 4 × 1 0 5 4 \times 10^5 4×105 | 无 |
| 11 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 4 × 1 0 5 4 \times 10^5 4×105 | 字符串随机 |
| 12 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 6 × 1 0 5 6 \times 10^5 6×105 | 无 |
| 13 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 6 × 1 0 5 6 \times 10^5 6×105 | 字符串随机 |
| 14 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 8 × 1 0 5 8 \times 10^5 8×105 | 无 |
| 15 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 8 × 1 0 5 8 \times 10^5 8×105 | 字符串随机 |
| 16 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 17 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 字符串随机 |
| 18 | 2 × 1 0 5 2 \times 10^5 2×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 19 | 3 × 1 0 5 3 \times 10^5 3×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 20 | 4 × 1 0 5 4 \times 10^5 4×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 21 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 22 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 23 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 24 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |
| 25 | 5 × 1 0 5 5 \times 10^5 5×105 | 1 0 5 10^5 105 | 1 0 6 10^6 106 | 无 |

对于前17个测试点的所有询问有 l = 1 , r = ∣ S ∣ l = 1 , r = ∣ S ∣ l=1,r=|S|l=1,r=∣S∣ l=1,r=Sl=1,r=S

对于所有数据,保证 1 ≤ l ≤ r ≤ ∣ S ∣ 1\leq l \leq r \leq |S| 1lrS, 1 ≤ ∣ T ∣ ≤ 5 × 1 0 5 1\leq |T|\leq 5 \times 10^5 1T5×105

解题分析

首先我们考虑如何处理 68 68 68分的情况, 显然我们可以先对 S S S建立 S A M SAM SAM, 再对 T T T建立 S A M SAM SAM, 并同时在 S S S上试图匹配到一个最长的后缀 s s s, 其长度为 l i m i lim_i limi,并记录其第一次出现的位置(方便确定分裂出去的子串能匹配的长度, 因为此时长度为 l e n [ i ] len[i] len[i]的子串并不是一个前缀)为 f i r [ i ] fir[i] fir[i]。这样的话设 c n t cnt cnt为最终 T T T自动机的大小, 最终的答案就是 ∑ i = 2 c n t l e n [ i ] − m a x ( l e n [ p a r [ i ] ] , l i m [ f i r [ i ] ] ) \sum_{i=2}^{cnt}len[i]-max(len[par[i]],lim[fir[i]]) i=2cntlen[i]max(len[par[i]],lim[fir[i]])

现在考虑加入 [ l , r ] [l,r] [l,r]这个限制。 我们发现唯一的改变就是我们在 S S S S A M SAM SAM上跳的时候这个后缀 s s s不一定在 [ l , r ] [l,r] [l,r]中出现过, 所以我们用可持久化线段树维护 S S S e n d p o s endpos endpos集合, 按拓扑序从下往上合并, 在确定 l i m I lim_I limI的时候不断跳 p a r e n t parent parent链来保证这个要求即可。

总复杂度 O ( ∣ S ∣ + ∑ ∣ T ∣ + ( ( ∑ ∣ T ∣ ) + ∣ S ∣ ) l o g ( ∣ S ∣ ) ) O(|S|+\sum|T|+((\sum|T|)+|S|)log(|S|)) O(S+T+((T)+S)log(S))

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 1000500
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc);
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
}
int L;
int root[MX], lim[MX], son[MX * 20][2];
char buf[MX];
namespace SGT
{
	int arr;
	#define ls son[now][0]
	#define rs son[now][1]
	void insert(int &now, R int lef, R int rig, R int tar)
	{
		if(!now) now = ++arr;
		if(lef == rig) return;
		int mid = lef + rig >> 1;
		if(tar <= mid) insert(ls, lef, mid, tar);
		else insert(rs, mid + 1, rig, tar);
	}
	int merge(R int x, R int y)
	{
		if(!x || !y) return x + y;
		int now = ++arr;
		ls = merge(son[x][0], son[y][0]);
		rs = merge(son[x][1], son[y][1]);
		return now;
	}
	bool query(R int now, R int lef, R int rig, R int lb, R int rb)
	{
		if(!now || lb > rb) return false;
		if(lb <= lef && rig <= rb) return true;
		int mid = lef + rig >> 1;
		if(lb <= mid) if(query(ls, lef, mid, lb, rb)) return true;
		if(rb > mid) if(query(rs, mid + 1, rig, lb, rb)) return true;
		return false;
	}
	#undef ls
	#undef rs
}
namespace SAM
{
	int to[MX][26], par[MX], len[MX], siz[MX], buc[MX], ord[MX];
	int cnt = 1, last = 1, cur, l;
	IN void insert(R int id)
	{
		R int now = last, tar;
		cur = ++cnt; len[cur] = len[last] + 1, siz[last = cur] = 1;
		for (; now && !to[now][id]; now = par[now]) to[now][id] = cur;
		if (!now) return par[cur] = 1, void();
		tar = to[now][id];
		if(len[tar] == len[now] + 1) return par[cur] = tar, void();
		int nw = ++cnt; len[nw] = len[now] + 1;
		par[nw] = par[tar], par[tar] = par[cur] = nw;
		std::memcpy(to[nw], to[tar], sizeof(to[nw]));
		for (; now && to[now][id] == tar; now = par[now]) to[now][id] = nw;
	}
	IN void build_SGT()
	{
		R int now;
		for (R int i = 1; i <= cnt; ++i) buc[len[i]]++;
		for (R int i = 1; i <= L; ++i) buc[i] += buc[i - 1];
		for (R int i = cnt; i > 1; --i) ord[buc[len[i]]--] = i;
		for (R int i = cnt; i > 1; --i)
		{
			now = ord[i];
			if(siz[now]) SGT::insert(root[now], 1, L, len[now]);
			root[par[now]] = SGT::merge(root[par[now]], root[now]);
		}
	}
}
namespace Solve
{
	int cnt, last, cur, l;
	int to[MX][26], par[MX], len[MX], fir[MX];
	IN void reset()
	{
		for (R int i = 1; i <= cnt; ++i)
		std::memset(to[i], 0, sizeof(to[i])), par[i] = 0;
		last = cnt = 1;
	}
	IN void insert(R int id)
	{
		R int now = last, tar;
		cur = ++cnt; len[cur] = len[last] + 1, fir[cur] = len[last = cur];
		for (; now && !to[now][id]; now = par[now]) to[now][id] = cur;
		if(!now) return par[cur] = 1, void();
		tar = to[now][id];
		if(len[tar] == len[now] + 1) return par[cur] = tar, void();
		int nw = ++cnt; len[nw] = len[now] + 1, fir[nw] = fir[tar];
		par[nw] = par[tar], par[tar] = par[cur] = nw;
		std::memcpy(to[nw], to[tar], sizeof(to[nw]));
		for(; now && to[now][id] == tar; now = par[now]) to[now][id] = nw;
	}
	IN ll calc(R int lef, R int rig)
	{
		ll ans = 0;
		reset(); l = std::strlen(buf + 1);
		R int now = 1, ln = 0, id;
		for (R int i = 1; i <= l; ++i)
		{
			id = buf[i] - 'a';
			insert(id);
			W (233)
			{
				if(SAM::to[now][id] && SGT::query(root[SAM::to[now][id]], 1, L, lef + ln, rig))//endpos 应该加上len 才是正确的位置集合
				{
					++ln;
					now = SAM::to[now][id];
					break;
				}
				if(!ln) break;
				--ln;
				if(ln == SAM::len[SAM::par[now]]) now = SAM::par[now];
			}
			lim[i] = ln;
		}
		for (R int i = 2; i <= cnt; ++i)
		ans += std::max(0, len[i] - std::max(len[par[i]], lim[fir[i]]));
		return ans;
	}
}
int main(void)
{
	scanf("%s", buf + 1);
	L = std::strlen(buf + 1);
	for (R int i = 1; i <= L; ++i)
	SAM::insert(buf[i] - 'a');
	SAM::build_SGT();
	int T, l, r;
	in(T);
	W (T--)
	{
		scanf("%s", buf + 1);
		in(l), in(r);
		printf("%lld\n", Solve::calc(l, r));
	}
}

你可能感兴趣的:(SAM,可持久化,线段树)