【题解】LOJ-2720-BZOJ-5417-Luogu-4770-「NOI2018」你的名字

题目

https://loj.ac/problem/2720

https://www.lydsy.com/JudgeOnline/problem.php?id=5417

https://www.luogu.org/problemnew/show/P4770

题意

给一个串 S,每次询问一个串 T 和 S 的一个子串 s,问 T 的所有本质不同且没在 s 中出现过的子串数量。

题解

Part 1 (68 分)

  • 对 T 建后缀自动机,如果光是 T 本质不同的子串个数就是 \(\sum_u len[u]-len[fa[u]]\)。这个结点所表示的可以看成 T 的一个前缀的某一个区间长度的后缀,而对于 T 的每一个前缀,会有一段后缀在 S 中出现过,把这一部分从中剔除就行了。
  • 对 S 建后缀自动机,用 T 在上面跑,得到 T 的每一个前缀的最长匹配后缀。设对于前缀 \(i\),最长匹配长度是 \(ml[i]\)。对于 T 中的自动机的结点,\(one[u]\) 表示 \(u\)\(EndPos\) 集合中的任意一个。那么答案就是 \(\sum_u \max\{0, len[u]-\max\{len[fa[u]], ml[one[u]]\}\}\)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
#ifdef zerol
#define dbg(args...) do { cout << "DBG: " << #args << " -> "; err(args); } while (0)
#else
#define dbg(...)
#endif
void err() { cout << endl; }
template<template<typename...> class T, typename t, typename... Args>
void err(T<t> a, Args... args) { for (auto x: a) cout << x << ' '; err(args...); }
template<typename T, typename... Args>
void err(T a, Args... args) { cout << a << ' '; err(args...); }
// -----------------------------------------------------------------------------
const int N = 2E6 + 100;

struct SAM {
int t[N][26], len[N] = {-1}, fa[N], sz = 2, last = 1, one[N];
void ins(int ch, int pp) {
int p = last, np = last = sz++; one[np] = pp;
len[np] = len[p] + 1;
for (; p && !t[p][ch]; p = fa[p]) t[p][ch] = np;
if (!p) { fa[np] = 1; return; }
int q = t[p][ch];
if (len[p] + 1 == len[q]) fa[np] = q;
else {
int nq = sz++; len[nq] = len[p] + 1;
memcpy(t[nq], t[q], sizeof t[0]); one[nq] = one[q];
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for (; t[p][ch] == q; p = fa[p]) t[p][ch] = nq;
}
}
void init() { memset(t, 0, (sz + 10) * sizeof t[0]); last = 1; sz = 2; }
} S, T;

char s[N], t[N];
int ml[N], ls, lt;

void get_ml() {
int u = 1, l = 0;
FOR (i, 0, lt) {
int ch = t[i] - 'a';
while (u && !S.t[u][ch]) { u = S.fa[u]; l = S.len[u]; }
++l; u = S.t[u][ch];
if (!u) u = 1;
ml[i] = l;
}
}

int main() {
#ifndef zerol
freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout);
#else
freopen("in", "r", stdin);
#endif
scanf("%s", s); ls = strlen(s);
FOR (i, 0, ls) S.ins(s[i] - 'a', i);
int Qn; cin >> Qn;
while (Qn--) {
T.init();

int l, r; scanf("%s%d%d", t, &l, &r); lt = strlen(t);
assert(l == 1 && r == ls);

FOR (i, 0, lt) T.ins(t[i] - 'a', i);

get_ml();

LL ans = 0;
FOR (i, 2, T.sz)
ans += max(0, T.len[i] - max(T.len[T.fa[i]], ml[T.one[i]]));
cout << ans << endl;
}
}

Part 2 (100 分)

  • 唯一影响到的只有在自动机上匹配的部分。即使有转移边也不一定能转移,因为那个结点能表示的所有串中无一在给定区间内。
  • 能转移的条件是那个结点的 \(EndPos\) 集合中存在 \([L + len, R]\) 的元素,其中 \(len\) 是当前匹配的长度。这部分用按 dfs序 建主席树维护。
  • 注意和之前代码不同,失败后不一定走 \(fa\),还可以仅仅减少 \(len\)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
#ifdef zerol
#define dbg(args...) do { cout << "DBG: " << #args << " -> "; err(args); } while (0)
#else
#define dbg(...)
#endif
void err() { cout << endl; }
template<template<typename...> class T, typename t, typename... Args>
void err(T<t> a, Args... args) { for (auto x: a) cout << x << ' '; err(args...); }
template<typename T, typename... Args>
void err(T a, Args... args) { cout << a << ' '; err(args...); }
// -----------------------------------------------------------------------------
const int N = 2E6 + 100;

namespace tree {
#define lson l, (l + r) / 2
#define rson (l + r) / 2 + 1, r
struct P { int w, ls, rs; } tr[N * 23];
int sz = 1;
int _new(int w, int ls, int rs) {
tr[sz] = {w, ls, rs};
return sz++;
}
int ins(int oo, int p, int l, int r) {
if (p > r || l > p) return oo;
const P& o = tr[oo];
if (l == r) return _new(o.w + 1, 0, 0);
return _new(o.w + 1, ins(o.ls, p, lson), ins(o.rs, p, rson));
}
bool query(int pp, int qq, int ql, int qr, int l, int r) {
if (ql > r || l > qr) return false;
const P &p = tr[pp], &q = tr[qq];
if (ql <= l && r <= qr) return q.w - p.w > 0;
return query(p.ls, q.ls, ql, qr, lson) || query(p.rs, q.rs, ql, qr, rson);
}
}

struct SAM {
int t[N][26], len[N] = {-1}, fa[N], sz = 2, last = 1, one[N], pos[N];
void ins(int ch, int pp) {
int p = last, np = last = sz++; one[np] = pp; pos[np] = pp;
len[np] = len[p] + 1;
for (; p && !t[p][ch]; p = fa[p]) t[p][ch] = np;
if (!p) { fa[np] = 1; return; }
int q = t[p][ch];
if (len[p] + 1 == len[q]) fa[np] = q;
else {
int nq = sz++; len[nq] = len[p] + 1;
memcpy(t[nq], t[q], sizeof t[0]); one[nq] = one[q];
fa[nq] = fa[q];
fa[np] = fa[q] = nq;
for (; t[p][ch] == q; p = fa[p]) t[p][ch] = nq;
}
}
void init() { memset(t, 0, (sz + 10) * sizeof t[0]); last = 1; sz = 2; }
} S, T;

char s[N], t[N];
int ml[N], ls, lt;
int rt[N], in[N], out[N], clk, ridx[N];
vector<int> G[N];
void dfs(int u) {
in[u] = ++clk; ridx[clk] = u;
for (int& v: G[u]) dfs(v);
out[u] = clk;
}

void get_ml(int L, int R) {
int u = 1, l = 0;
FOR (i, 0, lt) {
int ch = t[i] - 'a', _;
while (u && (!(_ = S.t[u][ch]) || !tree::query(rt[in[_] - 1], rt[out[_]], L + l, R, 0, N))) {
if (--l == S.len[S.fa[u]]) u = S.fa[u]; // !!!
}
++l; u = S.t[u][ch];
if (!u) u = 1;
ml[i] = l;
}
}

int main() {
freopen("name.in", "r", stdin);
freopen("name.out", "w", stdout);
memset(S.pos, -1, sizeof S.pos);
scanf("%s", s); ls = strlen(s);
FOR (i, 0, ls) S.ins(s[i] - 'a', i);
FOR (i, 2, S.sz) G[S.fa[i]].push_back(i);
dfs(1);
FOR (i, 1, S.sz) {
int pp = S.pos[ridx[i]];
rt[i] = pp == -1 ? rt[i - 1] : tree::ins(rt[i - 1], pp, 0, N);
}

int Qn; cin >> Qn;
while (Qn--) {
T.init();
int l, r; scanf("%s%d%d", t, &l, &r); lt = strlen(t);
FOR (i, 0, lt) T.ins(t[i] - 'a', i);
get_ml(l - 1, r - 1);
LL ans = 0;
FOR (i, 2, T.sz)
ans += max(0, T.len[i] - max(T.len[T.fa[i]], ml[T.one[i]]));
cout << ans << endl;
}
}