@loj - 2720@ 「NOI2018」你的名字


@description@

ION 每年规定一个命名串,要求每道题的名字必须是那一年的命名串的一个非空连续子串,且不能和前一年的任何一道题目的名字相同。
由于一些特殊的原因,小 A 得到了 ION2017 的命名串。
现在小 A 有 Q 次询问:每次给定 ION2017 的命名串和 ION2018 的命名串,求有几种题目的命名,使得这个名字是 ION2018 的命名串的一个非空连续子串且一定不会和 ION2017 的任何一道题目的名字相同。
由于一些特殊原因,所有询问给出的 ION2017 的命名串都是某个串的连续子串。

输入格式
从文件 name.in 读入数据。
第一行一个字符串 S,之后询问给出的 ION2017 的命名串都是 S 的连续子串。
第二行一个正整数 Q,表示询问次数。
接下来 Q 行,每行有一个字符串 T 和两个正整数 l, r,表示询问如果 ION2017 的命名串是 S[l, r],ION2018 的命名串是 T 的话,有几种命名方式一定满足规定。
保证输入中给出的字符串都是由小写字母构成的。

输出格式
输出到文件 name.out 中。
输出 Q 行,第 i 行一个非负整数表示第 i 个询问的答案。

样例输入 1
scbamgepe
3
smape 2 7
sbape 3 8
sgepe 1 9
样例输出 1
12
10
4

数据范围与提示
对于所有数据,保证 1 <= l <= r <= |S| <= 510^5, 1 <= |T| <= 510^5, ∑|T| <= 10^6, Q <= 10^5。

@solution@

先考虑 l = 1, r = |S| 的情况。

我们不妨对 S 建出后缀自动机,然后把 T 拿到 S 上跑一跑。对于 T 的每一个前缀 i,我们都求出一个最大的 f[i],使得 T 的前缀 i 的长度为 f[i] 的后缀是 S 的子串。
则显然前缀 i 长度 <= f[i] 的所有后缀也在 S 中出现过。

接着我们对 T 建出后缀自动机,然后把每一个前缀对应的结点上打上 f[i] 的 tag,然后沿着 parent 树传递 tag。
这样子就可以处理 T 的后缀自动机中每个结点对应的字符串有多少没有在 S 中出现。于是就可以解决题目的询问。

然后考虑给定的串是 S 的某个子串 S[l...r] 时,我们一样是考虑对于每个 i 求出 f[i]。
我们考虑使用可持久化线段树合并,求出 S 的后缀自动机上每一个结点的 end-pos 集合。

则当 T 的前缀 i 匹配上了 S 后缀自动机的结点 x,如果要尽可能在 S[l...r] 中匹配成功,我们肯定是选择 end-pos 集合中 <= r 且尽量大的那个。线段树上二分一下即可。
如果不存在这样一个 end-pos 或是这个 end-pos 对应的字符串超出了左端点,我们就当作匹配失败,继续沿着 parent 树跳。

这样下来,因为要用线段树,所以时间复杂度是 O(|T|*logn) 的。

@accepted code@

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1000000;
struct segtree{
	struct node{
		int ch[2], mx;
	}pl[20*MAXN + 5];
	int ncnt;
	segtree() {ncnt = 0; pl[0].mx = -1;}
	void pushup(int x) {
		if( pl[x].ch[1] ) pl[x].mx = pl[pl[x].ch[1]].mx;
		else pl[x].mx = pl[pl[x].ch[0]].mx;
	}
	int merge(int a, int b) {
		if( !a ) return b;
		if( !b ) return a;
		int c = (++ncnt);
		pl[c].ch[0] = merge(pl[a].ch[0], pl[b].ch[0]);
		pl[c].ch[1] = merge(pl[a].ch[1], pl[b].ch[1]);
		pushup(c);
		return c;
	}
	int insert(int a, int l, int r, int p) {
		int b = (++ncnt); pl[b].ch[0] = pl[a].ch[0], pl[b].ch[1] = pl[a].ch[1];
		if( l == r ) {
			pl[b].mx = l;
			return b;
		}
		int mid = (l + r) >> 1;
		if( p <= mid ) pl[b].ch[0] = insert(pl[a].ch[0], l, mid, p);
		else pl[b].ch[1] = insert(pl[a].ch[1], mid + 1, r, p);
		pushup(b);
		return b;
	}
	int query(int x, int l, int r, int p) {
		if( l == r ) return pl[x].mx;
		int mid = (l + r) >> 1;
		if( p <= mid ) return query(pl[x].ch[0], l, mid, p);
		else {
			int k = query(pl[x].ch[1], mid + 1, r, p);
			if( k != -1 ) return k;
			else return pl[pl[x].ch[0]].mx;
		}
	}
};
struct SAM{
	struct node{
		node *ch[26], *fa;
		int mx, pos, tag;
	}pl[2*MAXN + 5], *lst, *root, *ncnt;
	SAM() {ncnt = lst = root = &pl[0];}
	void clear() {
		int size = ncnt - pl + 1;
		for(int i=0;i<size;i++) {
			for(int j=0;j<26;j++)
				pl[i].ch[j] = NULL;
			pl[i].fa = NULL, pl[i].mx = pl[i].pos = pl[i].tag = 0;
		}
		ncnt = lst = root = &pl[0];
	}
	void extend(int c) {
		node *cur = (++ncnt), *p = lst; lst = cur;
		cur->mx = cur->pos = p->mx + 1;
		while( p && p->ch[c] == NULL )
			p->ch[c] = cur, p = p->fa;
		if( !p )
			cur->fa = root;
		else {
			node *q = p->ch[c];
			if( p->mx + 1 == q->mx )
				cur->fa = q;
			else {
				node *cne = (++ncnt); (*cne) = (*q);
				cne->mx = p->mx + 1; cne->pos = 0;
				cur->fa = q->fa = cne;
				while( p && p->ch[c] == q )
					p->ch[c] = cne, p = p->fa;
			}
		}
	}
	node *a[2*MAXN + 5]; int b[2*MAXN + 5];
	segtree T; int rt[2*MAXN + 5];
	void sort(int len) {
		int size = ncnt - pl + 1;
		for(int i=1;i<=len;i++) b[i] = 0;
		for(int i=0;i<size;i++) b[pl[i].mx]++;
		for(int i=1;i<=len;i++) b[i] += b[i-1];
		for(int i=0;i<size;i++) a[--b[pl[i].mx]] = &pl[i];
	}
	void build(int len) {
		int size = ncnt - pl + 1; sort(len);
		for(int i=0;i<size;i++)
			rt[i] = 0;
		for(int i=size-1;i>0;i--) {
			if( a[i]->pos )
				rt[a[i]-pl] = T.insert(rt[a[i]-pl], 1, len, a[i]->pos);
			rt[a[i]->fa-pl] = T.merge(rt[a[i]->fa-pl], rt[a[i]-pl]);
		}
	}
	long long solve(int len) {
		int size = ncnt - pl + 1; sort(len);
		long long ret = 0;
		for(int i=size-1;i>0;i--)
			a[i]->fa->tag = min(a[i]->fa->mx, max(a[i]->fa->tag, a[i]->tag)), ret += max(0, a[i]->mx - max(a[i]->fa->mx, a[i]->tag));
		return ret;
	}
	void debug(int len) {
		int size = ncnt - pl + 1;
		for(int i=0;i<size;i++) {
			printf("%d : %d %d %d %d\n", i, pl[i].fa-pl, pl[i].mx, pl[i].pos, pl[i].tag);
			//T.debug(rt[i], 1, len);
		}
		puts("");
	}
}S, T;
char str[MAXN + 5];
int mx[MAXN + 5];
int main() {
	freopen("name.in", "r", stdin);
	freopen("name.out", "w", stdout);
	scanf("%s", str);
	int lenS = strlen(str);
	for(int i=0;i<lenS;i++)
		S.extend(str[i] - 'a');
	S.build(lenS);
	int Q; scanf("%d", &Q);
	for(int i=1;i<=Q;i++) {
		int l, r, lenT;
		scanf("%s%d%d", str, &l, &r), lenT = strlen(str);
		SAM::node *nw = S.root; int tmp = 0, x;
		for(int j=0;j<lenT;j++) {
			while( nw && nw->ch[str[j]-'a'] == NULL )
				nw = nw->fa;
			if( nw ) {
				tmp = min(tmp, nw->mx) + 1, nw = nw->ch[str[j]-'a'];
				x = S.T.query(S.rt[nw-S.pl], 1, lenS, r);
				while( nw ) {
					if( x == -1 ) nw = nw->fa;
					else {
						if( nw == S.root || l + nw->fa->mx <= x ) break;
						else nw = nw->fa;
					}
					if( nw ) x = S.T.query(S.rt[nw-S.pl], 1, lenS, r);
				}
				if( nw )
					tmp = min(tmp, min(x - l + 1, nw->mx));
				else tmp = 0, nw = S.root;
			}
			else tmp = 0, nw = S.root;
			mx[j] = tmp;
		}
		for(int j=0;j<lenT;j++)
			T.extend(str[j]-'a');
		nw = T.root;
		for(int j=0;j<lenT;j++) {
			nw = nw->ch[str[j]-'a'];
			nw->tag = mx[j];
		}
		printf("%lld\n", T.solve(lenT));
		T.clear();
	}
}

@details@

因为要对 T 多次建后缀自动机,所以每次询问后要注意清空 T 的后缀自动机。

posted @ 2019-08-16 09:22  Tiw_Air_OAO  阅读(188)  评论(0编辑  收藏  举报