## 例题
-### [「HEOI2015」最短不公共子串](https://www.luogu.org/problem/P4112)
-
-这题的 (1) 和 (3) 两问需要后缀自动机,而且做法类似,在这里只讲解 (2) 和 (4) 两问。
-
-(2) 比较简单,枚举 A 的子串输入进 B 的序列自动机,若不接受则计入答案。
-
-(4) 需要 DP。令 $f(i, j)$ 表示在 A 的序列自动机中处于状态 $i$ ,在 B 的序列自动机中处于状态 $j$ ,需要再添加多少个字符能够不是公共子序列。
-
- $f(i, null)=0$
-
- $f(i, j)=\min\limits_{\delta_A(i,c)\ne null}f(\delta_A(i, c), \delta_B(j, c))$
-
-??? note "整道题的参考代码"
- ```cpp
- #include <algorithm>
- #include <cstdio>
- #include <cstring>
- #include <iostream>
-
- using namespace std;
-
- const int N = 2005;
-
- char s[N], t[N];
- int na[N][26], nb[N][26], nxt[26];
- int n, m, a[N], b[N], tot = 1, p = 1, f[N][N << 1];
-
- struct SAM {
- int par, ch[26], len;
- } sam[N << 1];
-
- void insert(int x) {
- int np = ++tot;
- sam[np].len = sam[p].len + 1;
- while (p && !sam[p].ch[x]) {
- sam[p].ch[x] = np;
- p = sam[p].par;
- }
- if (p == 0)
- sam[np].par = 1;
- else {
- int q = sam[p].ch[x];
- if (sam[q].len == sam[p].len + 1)
- sam[np].par = q;
- else {
- int nq = ++tot;
- sam[nq].len = sam[p].len + 1;
- memcpy(sam[nq].ch, sam[q].ch, sizeof(sam[q].ch));
- sam[nq].par = sam[q].par;
- sam[q].par = sam[np].par = nq;
- while (p && sam[p].ch[x] == q) {
- sam[p].ch[x] = nq;
+???+note "[「HEOI2015」最短不公共子串](https://www.luogu.org/problem/P4112)?
+
+ 给你两个由小写英文字母组成的串 $A$ 和 $B$,求:
+
+ 1. $A$ 的一个最短的子串,它不是 $B$ 的子串;
+ 2. $A$ 的一个最短的子串,它不是 $B$ 的子序列;
+ 3. $A$ 的一个最短的子序列,它不是 $B$ 的子串;
+ 4. $A$ 的一个最短的子序列,它不是 $B$ 的子序列。
+
+ $1\le |A|, |B|\le 2000$。
+
+ ??? mdui-shadow-6 "题解"
+ 这题的 (1) 和 (3) 两问需要后缀自动机,而且做法类似,在这里只讲解 (2) 和 (4) 两问。
+
+ (2) 比较简单,枚举 A 的子串输入进 B 的序列自动机,若不接受则计入答案。
+
+ (4) 需要 DP。令 $f(i, j)$ 表示在 A 的序列自动机中处于状态 $i$ ,在 B 的序列自动机中处于状态 $j$ ,需要再添加多少个字符能够不是公共子序列。
+
+ $f(i, null)=0$
+
+ $f(i, j)=\min\limits_{\delta_A(i,c)\ne null}f(\delta_A(i, c), \delta_B(j, c))$
+
+ ??? mdui-shadow-6 "参考代码"
+ ```cpp
+ #include <algorithm>
+ #include <cstdio>
+ #include <cstring>
+ #include <iostream>
+
+ using namespace std;
+
+ const int N = 2005;
+
+ char s[N], t[N];
+ int na[N][26], nb[N][26], nxt[26];
+ int n, m, a[N], b[N], tot = 1, p = 1, f[N][N << 1];
+
+ struct SAM {
+ int par, ch[26], len;
+ } sam[N << 1];
+
+ void insert(int x) {
+ int np = ++tot;
+ sam[np].len = sam[p].len + 1;
+ while (p && !sam[p].ch[x]) {
+ sam[p].ch[x] = np;
p = sam[p].par;
}
- }
- }
- p = np;
- }
-
- int main() {
- scanf("%s%s", s + 1, t + 1);
-
- n = strlen(s + 1);
- m = strlen(t + 1);
-
- for (int i = 1; i <= n; ++i) a[i] = s[i] - 'a';
- for (int i = 1; i <= m; ++i) b[i] = t[i] - 'a';
-
- for (int i = 1; i <= m; ++i) insert(b[i]);
-
- for (int i = 0; i < 26; ++i) nxt[i] = n + 1;
- for (int i = n; i >= 0; --i) {
- memcpy(na[i], nxt, sizeof(nxt));
- nxt[a[i]] = i;
- }
-
- for (int i = 0; i < 26; ++i) nxt[i] = m + 1;
- for (int i = m; i >= 0; --i) {
- memcpy(nb[i], nxt, sizeof(nxt));
- nxt[b[i]] = i;
- }
-
- int ans = N;
-
- for (int l = 1; l <= n; ++l) {
- for (int r = l, u = 1; r <= n; ++r) {
- u = sam[u].ch[a[r]];
- if (!u) {
- ans = min(ans, r - l + 1);
- break;
+ if (p == 0)
+ sam[np].par = 1;
+ else {
+ int q = sam[p].ch[x];
+ if (sam[q].len == sam[p].len + 1)
+ sam[np].par = q;
+ else {
+ int nq = ++tot;
+ sam[nq].len = sam[p].len + 1;
+ memcpy(sam[nq].ch, sam[q].ch, sizeof(sam[q].ch));
+ sam[nq].par = sam[q].par;
+ sam[q].par = sam[np].par = nq;
+ while (p && sam[p].ch[x] == q) {
+ sam[p].ch[x] = nq;
+ p = sam[p].par;
+ }
+ }
}
+ p = np;
}
- }
-
- printf("%d\n", ans == N ? -1 : ans);
-
- ans = N;
-
- for (int l = 1; l <= n; ++l) {
- for (int r = l, u = 0; r <= n; ++r) {
- u = nb[u][a[r]];
- if (u == m + 1) {
- ans = min(ans, r - l + 1);
- break;
+
+ int main() {
+ scanf("%s%s", s + 1, t + 1);
+
+ n = strlen(s + 1);
+ m = strlen(t + 1);
+
+ for (int i = 1; i <= n; ++i) a[i] = s[i] - 'a';
+ for (int i = 1; i <= m; ++i) b[i] = t[i] - 'a';
+
+ for (int i = 1; i <= m; ++i) insert(b[i]);
+
+ for (int i = 0; i < 26; ++i) nxt[i] = n + 1;
+ for (int i = n; i >= 0; --i) {
+ memcpy(na[i], nxt, sizeof(nxt));
+ nxt[a[i]] = i;
}
- }
- }
-
- printf("%d\n", ans == N ? -1 : ans);
-
- for (int i = n; i >= 0; --i) {
- for (int j = 1; j <= tot; ++j) {
- f[i][j] = N;
- for (int c = 0; c < 26; ++c) {
- int u = na[i][c];
- int v = sam[j].ch[c];
- if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+
+ for (int i = 0; i < 26; ++i) nxt[i] = m + 1;
+ for (int i = m; i >= 0; --i) {
+ memcpy(nb[i], nxt, sizeof(nxt));
+ nxt[b[i]] = i;
}
- }
- }
-
- printf("%d\n", f[0][1] == N ? -1 : f[0][1]);
-
- memset(f, 0, sizeof(f));
-
- for (int i = n; i >= 0; --i) {
- for (int j = 0; j <= m; ++j) {
- f[i][j] = N;
- for (int c = 0; c < 26; ++c) {
- int u = na[i][c];
- int v = nb[j][c];
- if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+
+ int ans = N;
+
+ for (int l = 1; l <= n; ++l) {
+ for (int r = l, u = 1; r <= n; ++r) {
+ u = sam[u].ch[a[r]];
+ if (!u) {
+ ans = min(ans, r - l + 1);
+ break;
+ }
+ }
+ }
+
+ printf("%d\n", ans == N ? -1 : ans);
+
+ ans = N;
+
+ for (int l = 1; l <= n; ++l) {
+ for (int r = l, u = 0; r <= n; ++r) {
+ u = nb[u][a[r]];
+ if (u == m + 1) {
+ ans = min(ans, r - l + 1);
+ break;
+ }
+ }
+ }
+
+ printf("%d\n", ans == N ? -1 : ans);
+
+ for (int i = n; i >= 0; --i) {
+ for (int j = 1; j <= tot; ++j) {
+ f[i][j] = N;
+ for (int c = 0; c < 26; ++c) {
+ int u = na[i][c];
+ int v = sam[j].ch[c];
+ if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+ }
+ }
+ }
+
+ printf("%d\n", f[0][1] == N ? -1 : f[0][1]);
+
+ memset(f, 0, sizeof(f));
+
+ for (int i = n; i >= 0; --i) {
+ for (int j = 0; j <= m; ++j) {
+ f[i][j] = N;
+ for (int c = 0; c < 26; ++c) {
+ int u = na[i][c];
+ int v = nb[j][c];
+ if (u <= n) f[i][j] = min(f[i][j], f[u][v] + 1);
+ }
+ }
}
+
+ printf("%d\n", f[0][0] == N ? -1 : f[0][0]);
+
+ return 0;
}
- }
-
- printf("%d\n", f[0][0] == N ? -1 : f[0][0]);
-
- return 0;
- }
- ```
+ ```
字典树最基础的应用——查找一个字符串是否在“字典”中出现过。
-#### [于是他错误的点名开始了](https://www.luogu.org/problemnew/show/P2580)
-
-对所有名字建 Trie,再在 Trie 中查询字符串是否存在,第一次点名时标记为点过名。
-
-??? note "参考代码"
- ```cpp
- #include <cstdio>
-
- const int N = 500010;
-
- char s[60];
- int n, m, ch[N][26], tag[N], tot = 1;
-
- int main() {
- scanf("%d", &n);
-
- for (int i = 1; i <= n; ++i) {
- scanf("%s", s + 1);
- int u = 1;
- for (int j = 1; s[j]; ++j) {
- int c = s[j] - 'a';
- if (!ch[u][c]) ch[u][c] = ++tot;
- u = ch[u][c];
+???+note "[于是他错误的点名开始了](https://www.luogu.org/problemnew/show/P2580)"
+ 给你 $n$ 个名字串,然后进行 $m$ 次点名,每次你需要回答“名字不存在”、“第一次点到这个名字”、“已经点过这个名字”之一。
+
+ $1\le n\le 10^4$, $1\le m\le 10^5$,所有字符串长度不超过 $50$。
+
+ ??? mdui-shadow-6 "题解"
+ 对所有名字建 Trie,再在 Trie 中查询字符串是否存在、是否已经点过名,第一次点名时标记为点过名。
+
+ ??? mdui-shadow-6 "参考代码"
+ ```cpp
+ #include <cstdio>
+
+ const int N = 500010;
+
+ char s[60];
+ int n, m, ch[N][26], tag[N], tot = 1;
+
+ int main() {
+ scanf("%d", &n);
+
+ for (int i = 1; i <= n; ++i) {
+ scanf("%s", s + 1);
+ int u = 1;
+ for (int j = 1; s[j]; ++j) {
+ int c = s[j] - 'a';
+ if (!ch[u][c]) ch[u][c] = ++tot;
+ u = ch[u][c];
+ }
+ tag[u] = 1;
+ }
+
+ scanf("%d", &m);
+
+ while (m--) {
+ scanf("%s", s + 1);
+ int u = 1;
+ for (int j = 1; s[j]; ++j) {
+ int c = s[j] - 'a';
+ u = ch[u][c];
+ if (!u) break; // 不存在对应字符的出边说明名字不存在
+ }
+ if (tag[u] == 1) {
+ tag[u] = 2;
+ puts("OK");
+ } else if (tag[u] == 2)
+ puts("REPEAT");
+ else
+ puts("WRONG");
+ }
+
+ return 0;
}
- tag[u] = 1;
- }
-
- scanf("%d", &m);
-
- while (m--) {
- scanf("%s", s + 1);
- int u = 1;
- for (int j = 1; s[j]; ++j) {
- int c = s[j] - 'a';
- u = ch[u][c];
- if (!u) break; // 不存在对应字符的出边说明名字不存在
- }
- if (tag[u] == 1) {
- tag[u] = 2;
- puts("OK");
- } else if (tag[u] == 2)
- puts("REPEAT");
- else
- puts("WRONG");
- }
-
- return 0;
- }
- ```
+ ```
### AC 自动机
将数的二进制表示看做一个字符串,就可以建出字符集为 $\{0,1\}$ 的 Trie 树。
-#### [BZOJ1954 最长异或路径](https://www.luogu.org/problem/P4551)
+???+note "[BZOJ1954 最长异或路径](https://www.luogu.org/problem/P4551)"
+ 给你一棵带边权的树,求 $(u, v)$ 使得 $u$ 到 $v$ 的路径上的边权异或和最大,输出这个最大值。
-随便指定一个根 $root$ ,用 $T(u, v)$ 表示 $u$ 和 $v$ 之间的路径的边权异或和,那么 $T(u,v)=T(root, u)\oplus T(root,v)$ ,因为 [LCA](../graph/lca.md) 以上的部分异或两次抵消了。
+ 点数不超过 $10^5$,边权在 $[0,2^{31})$ 内。
-那么,如果将所有 $T(root, u)$ 插入到一棵 Trie 中,就可以对每个 $T(root, u)$ 快速求出和它异或和最大的 $T(root, v)$ :
+ ??? mdui-shadow-6 "题解"
+ 随便指定一个根 $root$ ,用 $T(u, v)$ 表示 $u$ 和 $v$ 之间的路径的边权异或和,那么 $T(u,v)=T(root, u)\oplus T(root,v)$ ,因为 [LCA](../graph/lca.md) 以上的部分异或两次抵消了。
-从 Trie 的根开始,如果能向和 $T(root, u)$ 的当前位不同的子树走,就向那边走,否则没有选择。
+ 那么,如果将所有 $T(root, u)$ 插入到一棵 Trie 中,就可以对每个 $T(root, u)$ 快速求出和它异或和最大的 $T(root, v)$ :
-贪心的正确性:如果这么走,这一位为 $1$ ;如果不这么走,这一位就会为 $0$ 。而高位是需要优先尽量大的。
+ 从 Trie 的根开始,如果能向和 $T(root, u)$ 的当前位不同的子树走,就向那边走,否则没有选择。
-??? note "参考代码"
- ```cpp
- #include <algorithm>
- #include <cstdio>
-
- const int N = 100010;
-
- int head[N], nxt[N << 1], to[N << 1], weight[N << 1], cnt;
- int n, dis[N], ch[N << 5][2], tot = 1, ans;
-
- void insert(int x) {
- for (int i = 30, u = 1; i >= 0; --i) {
- int c = ((x >> i) & 1);
- if (!ch[u][c]) ch[u][c] = ++tot;
- u = ch[u][c];
- }
- }
-
- void get(int x) {
- int res = 0;
- for (int i = 30, u = 1; i >= 0; --i) {
- int c = ((x >> i) & 1);
- if (ch[u][c ^ 1]) {
- u = ch[u][c ^ 1];
- res |= (1 << i);
- } else
- u = ch[u][c];
- }
- ans = std::max(ans, res);
- }
-
- void add(int u, int v, int w) {
- nxt[++cnt] = head[u];
- head[u] = cnt;
- to[cnt] = v;
- weight[cnt] = w;
- }
-
- void dfs(int u, int fa) {
- insert(dis[u]);
- get(dis[u]);
- for (int i = head[u]; i; i = nxt[i]) {
- int v = to[i];
- if (v == fa) continue;
- dis[v] = dis[u] ^ weight[i];
- dfs(v, u);
- }
- }
-
- int main() {
- scanf("%d", &n);
-
- for (int i = 1; i < n; ++i) {
- int u, v, w;
- scanf("%d%d%d", &u, &v, &w);
- add(u, v, w);
- add(v, u, w);
- }
-
- dfs(1, 0);
-
- printf("%d", ans);
-
- return 0;
- }
- ```
+ 贪心的正确性:如果这么走,这一位为 $1$ ;如果不这么走,这一位就会为 $0$ 。而高位是需要优先尽量大的。
+
+ ??? mdui-shadow-6 "参考代码"
+ ```cpp
+ #include <algorithm>
+ #include <cstdio>
+
+ const int N = 100010;
+
+ int head[N], nxt[N << 1], to[N << 1], weight[N << 1], cnt;
+ int n, dis[N], ch[N << 5][2], tot = 1, ans;
+
+ void insert(int x) {
+ for (int i = 30, u = 1; i >= 0; --i) {
+ int c = ((x >> i) & 1);
+ if (!ch[u][c]) ch[u][c] = ++tot;
+ u = ch[u][c];
+ }
+ }
+
+ void get(int x) {
+ int res = 0;
+ for (int i = 30, u = 1; i >= 0; --i) {
+ int c = ((x >> i) & 1);
+ if (ch[u][c ^ 1]) {
+ u = ch[u][c ^ 1];
+ res |= (1 << i);
+ } else
+ u = ch[u][c];
+ }
+ ans = std::max(ans, res);
+ }
+
+ void add(int u, int v, int w) {
+ nxt[++cnt] = head[u];
+ head[u] = cnt;
+ to[cnt] = v;
+ weight[cnt] = w;
+ }
+
+ void dfs(int u, int fa) {
+ insert(dis[u]);
+ get(dis[u]);
+ for (int i = head[u]; i; i = nxt[i]) {
+ int v = to[i];
+ if (v == fa) continue;
+ dis[v] = dis[u] ^ weight[i];
+ dfs(v, u);
+ }
+ }
+
+ int main() {
+ scanf("%d", &n);
+
+ for (int i = 1; i < n; ++i) {
+ int u, v, w;
+ scanf("%d%d%d", &u, &v, &w);
+ add(u, v, w);
+ add(v, u, w);
+ }
+
+ dfs(1, 0);
+
+ printf("%d", ans);
+
+ return 0;
+ }
+ ```
### 可持久化字典树