【luogu P4770】[NOI2018] 你的名字(SAM)(线段树合并)

[NOI2018] 你的名字

题目链接:luogu P4770

题目大意

给你一个字符串 S,然后每次询问给你一个字符串 T 和 l,r。
要你求 T 中有多少个子串不是 S[l]~S[r] 组成的字符串的子串。

思路

首先我们看到很多的部分分是 \(l=1,r=|S|\)
那我们先想想怎么搞这个,也就是求 T 中有多少个子串不是 S 的子串。

首先考虑反过来求有多少个串同时是 S,T 的子串。
然后我们考虑把 T 在 S 的 SAM 上面跑,然后每个位置有匹配长度 \(len\),然后对于 T 的 SAM 的每个点我们记录一个 \(pl_i\) 表示这个点是字符串哪个前缀跑出来的点,然后跑到这个位置的 \(len\) 就是能匹配的长度 \(ans_i\)
然后答案就是枚举 \(T\) 的每个点(\(1\) 号点除外),贡献是 \(\max(0,len_i-\max(len_{fa_i},ans_{pl_i}))\)(这个 \(\max(0)\) 是防止 \(ans_pl_i\) 太大)

那复杂度为 \(O(|T|)\) 可以接受。

接着考虑 \(l,r\) 随便的情况。
那我们考虑会有什么问题,就是求这个 \(ans_i\) 的时候,你跑出来的点不一定行。
你要这个点是在 \(S_{l\sim r}\) 的 SAM 上才可以,亦或者说要这个点的 endpos 类里面有 \(l\sim r\) 的信息。
那要怎么判呢,我们考虑维护 S 的 SAM 中一个点的 endpos。
怎么维护呢,我们可以用动态开点线段树,然后父亲的 endpos 是自己并上儿子的 endpos,可以用线段树合并来解决。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N = 5e5 + 10;
char s[N], t[N];
int n, m, q, L, R, rt[N << 1], ans[N << 1];

struct XD_tree {
	int tot, val[N << 6], ls[N << 6], rs[N << 6];
	
	int insert(int x, int l, int r, int pl) {
		int now = ++tot; val[now] = val[x]; ls[now] = ls[x]; rs[now] = rs[x];
		val[now] = max(val[now], pl);
		if (l == r) return now;
		int mid = (l + r) >> 1;
		if (pl <= mid) ls[now] = insert(ls[now], l, mid, pl);
			else rs[now] = insert(rs[now], mid + 1, r, pl);
		return now;
	}
	
	int query(int now, int l, int r, int L, int R) {
		if (!now) return 0;
		if (L <= l && r <= R) return val[now];
		int mid = (l + r) >> 1, re = 0;
		if (L <= mid) re = max(re, query(ls[now], l, mid, L, R));
		if (mid < R) re = max(re, query(rs[now], mid + 1, r, L, R));
		return re;
	}
	
	int merge(int x, int y) {
		if (!x || !y) return x + y;
		int re = ++tot; val[re] = max(val[x], val[y]);
		ls[re] = merge(ls[x], ls[y]); rs[re] = merge(rs[x], rs[y]);
		return re;
	}
}TT;

struct SAM {
	int lst, tot;
	struct node {
		int son[26], len, fa, pl;
	}d[N << 1];
	
	void Init() {
		lst = tot = 1;
		memset(d[1].son, 0, sizeof(d[1].son));
	}
	
	int insert(char c, int pla) {
		int p = lst, np = ++tot; lst = np;
		memset(d[np].son, 0, sizeof(d[np].son));
		d[np].len = d[p].len + 1; d[np].pl = pla;
		for (; p && !d[p].son[c]; p = d[p].fa) d[p].son[c] = np;
		if (!p) d[np].fa = 1;
			else {
				int q = d[p].son[c];
				if (d[q].len == d[p].len + 1) d[np].fa = q;
					else {
						int nq = ++tot; d[nq] = d[q];
						d[nq].len = d[p].len + 1;
						d[np].fa = d[q].fa = nq;
						for (; p && d[p].son[c] == q; p = d[p].fa) d[p].son[c] = nq;	
					}
			}
		return lst;
	}
	
	void build() {
		static int tmp[N], xl[N << 1];
		for (int i = 1; i <= n; i++) tmp[i] = 0;
		for (int i = 1; i <= tot; i++) tmp[d[i].len]++;
		for (int i = 1; i <= n; i++) tmp[i] += tmp[i - 1];
		for (int i = 1; i <= tot; i++) xl[tmp[d[i].len]--] = i;
		for (int i = tot; i >= 1; i--) {//线段树合并来求SAM每个点的endpos
			int x = xl[i];
			rt[d[x].fa] = TT.merge(rt[d[x].fa], rt[x]);
		}
	}
	
	void Run(int &now, int &sz, int c) {
		while (now) {
			if (d[now].son[c]) {
				int x = TT.query(rt[d[now].son[c]], 1, n, 1, R) - L + 1;
				if (d[d[now].fa].len < x) {//不仅要跳到有这个儿子,还需要这个儿子的位置中有 L~R(前面这个比较的要小于这样减的时候【DP】才会是正的)
					now = d[now].son[c]; sz = min(sz + 1, x);
					return ;
				}
			}
			now = d[now].fa; sz = d[now].len;
		}
		now = 1; sz = 0;
	}
	
	ll work() {
		ll re = 0;
		for (int i = 2; i <= tot; i++)
			re += max(d[i].len - max(d[d[i].fa].len, ans[d[i].pl]), 0);
		return re;
	}
}S, T;

int main() {
	scanf("%s", s + 1); n = strlen(s + 1);
	S.Init();
	for (int i = 1; i <= n; i++) {
		int pl = S.insert(s[i] - 'a', 0);
		rt[pl] = TT.insert(rt[pl], 1, n, i); 
	}
	S.build();
	
	scanf("%d", &q);
	while (q--) {
		scanf("%s", t + 1); m = strlen(t + 1);
		scanf("%d %d", &L, &R);
		
		T.Init();
		for (int i = 1; i <= m; i++)
			T.insert(t[i] - 'a', i);
		int now = 1, sz = 0;
		for (int i = 1; i <= m; i++) {
			S.Run(now, sz, t[i] - 'a');
			ans[i] = sz;
		}
		printf("%lld\n", T.work());
	}
	
	return 0;
}
posted @ 2022-03-21 19:31  あおいSakura  阅读(38)  评论(0编辑  收藏  举报