[NOI2018] 你的名字

Description

给定一个文本串 \(s\) 和询问次数 \(q\) ,每次给定小文本串 \(t\) 和一个区间 \(l\)\(r\) ,求 \(t\) 有多少子串不是 \(s\) 在区间 \([l,\ r]\) 内的子串。

\(68\%\) 的数据区间是 \([1,\ |s|]\)

\(|s|,\ |t| \leq 5 \cdot 10 ^ 5,\ \ q\leq 10 ^ 5,\ \ \sum |t| \leq 10 ^ 6\)

Solution

\(68\ {\rm pts}\)

显然先对 \(s\) 建立 \({\rm SAM}\) ,反正询问都是对于整个串而言的。并且还可以对所有 \(t\) 建立 \({\rm SAM}\) ,都是可以接受的。

我们发现假如我们要选择 \(t\) 的所有 \({\rm SAM}\) 建好之后才去计算的话,涉及的步骤有亿点点麻烦(具体搞法可以去俺的博客这里找前半部分)。那我们想想能不能在建立的过程中就记录一些必要的信息呢??

能!

我们想要的仅是在第 \(i\) 个字符加进来后 \(endpos\) 第一个元素是 \(i\) 的那些子串。因为 \(link\) 上面所有子串在之前出现过,也必定在 \(i\) 处出现,所以在更靠前的点上已经能记录贡献了,要刨掉这种。

所以在加完第 \(i\) 个字符之后,紧接着记录父亲的最大子串长度,即 \(len(link(cur))\) 的值为数组 \(ha(i)\) ,表示这个状态前要刨掉的长度。正好此时 \(endpos\) 第一个元素为 \(i\) 的子串必然已全部出现,防止后面有 \(clone\) 状态捣乱。

所以我们只需要正常的向求最大公共子串那样求就行了,求完以此把贡献加上 \(lth - ha(i)\)

Code1

Code1

/*
这是 68 pts 的代码
scbamgepe
2
sgepe 1 9
sgepe 1 9
*/
#include 
using namespace std;
typedef long long ll;
const int N = 2e6 + 10;
ll ans, res, ha[N];
inline int read() {
	char ch = getchar();
	int s = 0, w = 1;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
	while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
	return s * w;
}
struct SAM {
	int n, cnt, las, len[N], link[N], ch[N][26];
	char s[N]; int mx[N];
	inline void init() {
		cnt = las = 1;
		memset(ch[1], 0, sizeof(ch[1]));
	}
	inline void SAM_stru(int c) {
		int cur = ++cnt, p = las;
		memset(ch[cur], 0, sizeof(ch[cur]));
		las = cur;
		len[cur] = len[p] + 1;
		while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
		if (!p) {link[cur] = 1; return ;}
		int q = ch[p][c];
		if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
		int clo = ++cnt;
		link[clo] = link[q]; len[clo] = len[p] + 1;
		link[q] = link[cur] = clo;
		memcpy(ch[clo], ch[q], sizeof(ch[clo]));
		while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
	}
} s, t;
inline void mian() {
	scanf("%s", t.s); t.n = strlen(t.s); t.init(); ans = res = 0;
	int l = read(), r = read();
	if (l != 1 || r != s.n) return ;
	for (int i = 0; i < t.n; ++i) t.SAM_stru(t.s[i] - 'a'), ha[i] = t.len[t.link[t.las]];
	int v = 1, lth = 0;
	for (int i = 0, c; i < t.n; ++i) {
		c = t.s[i] - 'a';
		if (s.ch[v][c]) v = s.ch[v][c], ++lth;
		else {
			while (v && !s.ch[v][c]) v = s.link[v];
			if (v) lth = s.len[v] + 1, v = s.ch[v][c];
			else v = 1, lth = 0;
		}
		ans += max(lth - ha[i], 0ll);
	}
	for (int i = 1; i <= t.cnt; ++i) {
		res += t.len[i] - t.len[t.link[i]];
	}
	printf("%lld\n", res - ans);
}
int main() {
	scanf("%s", s.s); s.n = strlen(s.s); s.init();
	for (int i = 0; i < s.n; ++i) s.SAM_stru(s.s[i] - 'a');
	int T = read();
	while (T--) mian();
	return 0;
}

\(100\ {\rm pts}\)

我们发现转到区间上的时候,原本 \({\rm SAM}\) 上的每个状态里的 \(endpos\) 不一定每个都满足;并且可能区间 \({\rm SAM}\) 的真正样子会与全串 \({\rm SAM}\) 大相径庭;还有可能自己没有贡献,但是因为 \(link\) 多了一个很关键的 \(endpos\)\(link\) 会有贡献。

大概是这三个大问题,我们来分别解决。

  1. \(endpos\) 不能全部匹配
  • 实际上只要我们能够快速得到一个状态的 \(endpos\) ,或者说只要能维护单状态在区间内最大的 \(endpos\) 位置就行。

  • 直接干算肯定不现实,想办法利用 \({\rm SAM}\)\(link\) 之间的关系,所以可以考虑线段树合并。

  • 这样的话每次失配跳 \(link\) 的时候再加一个判断在区间内是否存在一个 \(endpos\) 合法就行。

  1. 真实样貌大相径庭
  • 意思就是说,我们如果直接跳 \(link\) 可能因为形态不一样导致问题。但是似乎这种写法不会有问题,因为每个结点的贡献彼此分离,有贡献就有,没有就没有。
  1. 自己没有贡献 \(link\)
  • 大概就是说找到的 \(endpos\) 因为串长太大会被 \(L\) 限制导致干不过 \(ha\) ,从而没有贡献。但是 \(link\) 可能有一个更好的 \(endpos\) 或者说长度减小到有贡献了。所以说我们目前被 \(L\) 限制的话,继续跳 \(link\) 算贡献。

  • 时间复杂度均摊是线性,然鹅这个 sb 不会证,所以就咕了。

至此,问题已经全部解决,这道题也就搞定啦!

Code

Code2

/*
scbamgepe
2
sgepe 1 9
sgepe 1 9
*/
#include 
using namespace std;
typedef long long ll;
const int N = 2e6 + 10, M = 4e7 + 10;
ll ans, res, ha[N], tr[M];
int rt[N], tot, ls[M], rs[M];
inline int read() {
	char ch = getchar();
	int s = 0, w = 1;
	while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
	while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
	return s * w;
}
inline void pushup(int now) {tr[now] = max(tr[ls[now]], tr[rs[now]]);}
void modify(int &now, int lt, int rt, int it) {
	if (!now) tr[now = ++tot] = -1;
	if (lt == rt) {
		tr[now] = lt; return ;
	}
	int mid = (lt + rt) >> 1;
	if (it <= mid) modify(ls[now], lt, mid, it);
	else modify(rs[now], mid + 1, rt, it);
	pushup(now);
}
int query(int now, int lt, int rt, int L, int R) {
	if (!now) return -1;
	if (L <= lt && rt <= R) return tr[now];
	int mid = (lt + rt) >> 1, res = -1, ret = -1;
	if (L <= mid) res = query(ls[now], lt, mid, L, R);
	if (R > mid) ret = query(rs[now], mid + 1, rt, L, R);
	return max(res, ret);
}
int merge(int u, int v, int lt, int rt) {
	if (!u) return v;
	if (!v) return u;
	if (lt == rt) return tr[u] |= tr[v], u;
	int mid = (lt + rt) >> 1, w = ++tot;
	ls[w] = merge(ls[u], ls[v], lt, mid);
	rs[w] = merge(rs[u], rs[v], mid + 1, rt);
	tr[w] = max(tr[u], tr[v]);
	return w;
}
struct SAM {
	int n, cnt, las, len[N], link[N], ch[N][26];
	char s[N]; int tong[N], rk[N], mx[N];
	inline void init() {
		cnt = las = 1;
		memset(ch[1], 0, sizeof(ch[1]));
	}
	inline void SAM_stru(int c) {
		int cur = ++cnt, p = las;
		memset(ch[cur], 0, sizeof(ch[cur]));
		las = cur;
		len[cur] = len[p] + 1;
		while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
		if (!p) {link[cur] = 1; return ;}
		int q = ch[p][c];
		if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
		int clo = ++cnt;
		link[clo] = link[q]; len[clo] = len[p] + 1;
		link[q] = link[cur] = clo;
		memcpy(ch[clo], ch[q], sizeof(ch[clo]));
		while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
	}
	inline void Tong_sort() {
		for (int i = 2; i <= cnt; ++i) ++tong[len[i]];
		for (int i = 2; i <= cnt; ++i) tong[i] += tong[i - 1];
		for (int i = 2; i <= cnt; ++i) rk[tong[len[i]]--] = i;
		for (int i = cnt, v, u; i >= 2; --i) {
			v = rk[i]; u = link[v];
			rt[u] = merge(rt[u], rt[v], 0, n - 1);
		}
	}
} s, t;
inline void solve(int l, int r) {
	int v = 1, lth = 0;
	for (int i = 0, c, p, q; i < t.n; ++i) {
		c = t.s[i] - 'a';
		while (v && (!s.ch[v][c] || (p = query(rt[s.ch[v][c]], 0, s.n - 1, l, r)) == -1))
			v = s.link[v], lth = s.len[v];
		if (!v) {v = 1, lth = 0; continue;}
		++lth; v = s.ch[v][c];
		ll mat = min(p - l + 1, lth);
		ans += max(mat - ha[i], 0ll);
		if (lth > p - l + 1) {
			mat = max(mat, ha[i]);
			int u = s.link[v], let = s.len[u];
			while (u) {
				q = query(rt[u], 0, s.n - 1, l, r);
				ll mac = min(q - l + 1, let);
				ans += max(mac - mat, 0ll);
				if (mac <= mat && let <= q - l + 1) break;
				mat = max(mac, ha[i]);
				u = s.link[u]; let = s.len[u];
			}
		}
	}
	for (int i = 1; i <= t.cnt; ++i) {
		res += t.len[i] - t.len[t.link[i]];
	}
	printf("%lld\n", res - ans);
}
inline void mian() {
	scanf("%s", t.s); t.n = strlen(t.s); t.init(); ans = res = 0;
	int l = read(), r = read();
	for (int i = 0; i < t.n; ++i) {
		t.SAM_stru(t.s[i] - 'a');
		ha[i] = t.len[t.link[t.las]];
	}
	if (l != 1 || r != s.n) return solve(l - 1, r - 1), void();
	int v = 1, lth = 0;
	for (int i = 0, c; i < t.n; ++i) {
		c = t.s[i] - 'a';
		if (s.ch[v][c]) v = s.ch[v][c], ++lth;
		else {
			while (v && !s.ch[v][c]) v = s.link[v];
			if (v) lth = s.len[v] + 1, v = s.ch[v][c];
			else v = 1, lth = 0;
		}
		ans += max(lth - ha[i], 0ll);
	}
	for (int i = 1; i <= t.cnt; ++i) {
		res += t.len[i] - t.len[t.link[i]];
	}
	printf("%lld\n", res - ans);
}
int main() {
	scanf("%s", s.s); s.n = strlen(s.s); s.init();
	for (int i = 0; i < s.n; ++i) {
		s.SAM_stru(s.s[i] - 'a');
		modify(rt[s.las], 0, s.n - 1, i);
	}
	s.Tong_sort();
	int T = read();
	while (T--) mian();
	return 0;
}

posted @ 2022-02-17 20:56  Illusory_dimes  阅读(18)  评论(0编辑  收藏  举报