查表映射:将原数值 x 通过随机数得到一个新的随机数值 cx 并存入表中,以打乱原来可能有的某些性质。
位运算映射:将原数值 x 通过位运算得到一个新的数值 shift(x),以打乱原来可能有的某些性质。
typedefunsignedlonglong u64;
u64 mask = std::mt19937_64((unsigned)time(0))();
u64 shift(u64 x) {
x ^= mask;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
x ^= mask;
return x;
}
字符串哈希:字符串哈希要求有序。将字符串 s 视为一个 base 进制数,即 f(s)=∑si⋅basel−i(modM)。模数 M 尽量大,尽量为质数。
int net[N];
voidkmp_init() {
net[1] = 0;
for (int i = 2, j = 0; i <= m; i ++) {
while (j > 0 && b[j + 1] != b[i]) j = net[j];
if (b[j + 1] == b[i]) j ++;
net[i] = j;
}
}
int f[N];
voidkmp() {
for (int i = 1, j = 0; i <= n; i ++) {
while (j > 0 && (j == m || b[j + 1] != a[i])) j = net[j];
if (b[j + 1] == a[i]) j ++;
f[i] = j;
}
}
Z 算法
Z 函数:字符串 s 和 s[i:n] 的最长公共前缀(LCP)长度,记作 Zi。特别地,Z1=0。
流程:考虑从前往后扫。维护一个右端点最靠右的、和前缀匹配的子串。不妨记作 b[l,r]。
若 r<i,则
Zi≥0
若 r≥i,则
Zi≥min(Zi−l+1,r−i+1)
时间复杂度:注意到单次枚举的次数为 max{l+Zl−1} 的增量,故时间复杂度为 O(n)。
int Z[N];
voidZ_init() {
Z[1] = 0;
for (int i = 2, l = 0, r = 0; i <= m; i ++) {
if (r < i)
Z[i] = 0;
else
Z[i] = std::min(Z[i - l + 1], r - i + 1);
while (i + Z[i] <= m && b[1 + Z[i]] == b[i + Z[i]]) Z[i] ++;
if (i + Z[i] - 1 > r)
l = i, r = i + Z[i] - 1;
}
}
int p[N];
voidZ_algorithm() {
for (int i = 1, l = 0, r = 0; i <= n; i ++) {
if (r < i)
p[i] = 0;
else
p[i] = std::min(Z[i - l + 1], r - i + 1);
while (i + p[i] <= n && b[1 + p[i]] == a[i + p[i]]) p[i] ++;
if (i + p[i] - 1 > r)
l = i, r = i + p[i] - 1;
}
}
namespace trie {
int nClock;
structnode {int trans[26];
} t[N];
voidinsert(char *s) {
int p = 1, len = strlen(s);
for (int i = 0; i < len; i ++) {
int v = s[i] - 'a';
if (!t[p].trans[v]) t[p].trans[v] = ++ nClock;
p = t[p].trans[v];
}
}
}
0/1 trie 支持持久化,可以作为 0/1 trie 的前缀和来使用。
namespace trie {
constint pond = ...;
int nClock, root[N];
structnode {int trans[2];
int cnt;
} t[pond];
voidinsert(int &p, int q, int dep, int x) {
p = ++ nClock, t[p] = t[q];
t[p].cnt ++;
if (dep < 0) return;
int v = x >> dep & 1;
insert(t[p].trans[v], t[q].trans[v], dep - 1, x);
}
}
namespace trie {
constint pond = ...;
int nClock, root[N];
structnode {int trans[2];
int cnt;
#define lc trans[0]#define rc trans[1]
} t[pond];
voidinsert(int &p, int dep, int x) {
if (!p) p = ++ nClock;
t[p].cnt ++;
if (dep < 0) return;
int v = x >> dep & 1;
insert(t[p].trans[v], dep - 1, x);
}
intmerge(int p, int q) {
if (!p || !q) return p ^ q;
t[p].cnt += t[q].cnt;
t[p].lc = merge(t[p].lc, t[q].lc);
t[p].rc = merge(t[p].rc, t[q].rc);
return p;
}
/* 可持久化 0/1 trie 合并
int merge(int p, int q) {
if (!p || !q) return p ^ q;
int u = ++ nClock;
t[u].cnt = t[p].cnt + t[q].cnt;
t[u].lc = merge(t[p].lc, t[q].lc);
t[u].rc = merge(t[p].rc, t[q].rc);
return u;
}
*/
}
0/1 trie 中查询,与 x 异或的最大值:从高位→低位贪心,有异走异,无异走同。
namespace trie {
constint pond = ...;
int nClock, root;
structnode {int trans[2];
} t[pond];
voidinsert(int &p, int dep, int x) {
if (!p) p = ++ nClock;
if (dep < 0) return;
int v = x >> dep & 1;
insert(t[p].trans[v], dep - 1, x);
}
intask(int p, int dep, int x) {
if (dep < 0) return0;
int v = x >> dep & 1;
if (t[p].trans[v ^ 1])
return ask(t[p].trans[v ^ 1], dep - 1, x) + (1 << dep);
elsereturn ask(t[p].trans[v], dep - 1, x);
}
}
0/1 trie 中查询,与 x 异或的第 k 大值:从高位→低位考虑
设当前考虑到第 i 位,设 x 的异位里有 cnt 个数:
若 k≤cnt,则寻找 x 的异位中与 x 异或的第 k 大值。
若 k>cnt,则寻找 x 的同位中与 x 异或的第 k−cnt 大值。
namespace trie {
constint pond = ...;
int nClock, root;
structnode {int trans[2];
int cnt;
} t[pond];
voidinsert(int &p, int dep, int x) {
if (!p) p = ++ nClock;
t[p].cnt ++;
if (dep < 0) return;
int v = x >> dep & 1;
insert(t[p].trans[v], dep - 1, x);
}
intask(int p, int dep, int x, int k) {
if (dep < 0) return0;
int v = x >> dep & 1;
if (k <= t[t[p].trans[v ^ 1]].cnt)
return ask(t[p].trans[v ^ 1], dep - 1, x, k) + (1 << dep);
elsereturn ask(t[p].trans[v], dep - 1, x, k - t[t[p].trans[v ^ 1]].cnt);
}
}
0/1 trie 中查询,与 x 异或 ≥k 的信息:从高位→低位考虑
设当前考虑到第 i 位:
若 k 的第 i 位为 1,走 x 的异位。
若 k 的第 i 位为 0,计算 x 的异位的贡献,走 x 的同位。
考虑完所有位之后,计算当前叶子节点的贡献。
namespace trie {
constint pond = ...;
int nClock, root;
structnode {int trans[2];
} t[pond];
voidinsert(int &p, int dep, int x) {
if (!p) p = ++ nClock;
if (dep < 0) return;
int v = x >> dep & 1;
insert(t[p].trans[v], dep - 1, x);
}
intask(int p, int dep, int x, int k) {
if (dep < 0) return// Calculate the data of node pint v = x >> dep & 1;
if (k >> dep & 1)
return ask(t[p].trans[v ^ 1], dep - 1, x, k);
elsereturn ask(t[p].trans[v], dep - 1, x, k) + // Calculate the data of node t[p].trans[v ^ 1]
}
}
0/1 trie 中修改,令全局异或 x:从高位→低位考虑,运用懒标记,设当前考虑到第 i 位,若懒标记的第 i 位为 1,则交换左右儿子。
namespace trie {
constint pond = ...;
int nClock, root;
structnode {int trans[2];
int tag;
#define lc trans[0]#define rc trans[1]voidmk_tag(int x, int dep) {
if (dep < 0) return;
if (x >> dep & 1) std::swap(lc, rc);
tag ^= x;
}
} t[pond];
voidspread(int p, int dep) {
if (t[p].tag) {
t[t[p].lc].mk_tag(t[p].tag, dep - 1);
t[t[p].rc].mk_tag(t[p].tag, dep - 1);
t[p].tag = 0;
}
}
}
0/1 trie 中维护集合异或和,支持插入、删除、全局 +1:从低位→高位考虑
需要维护:
trans:转移边。
cnt:以当前状态为根的子树内的结点数。
xr:以当前状态为根的子树内的异或和。
插入、删除:递归操作即可。
全局 +1:交换当前节点的 0/1 儿子,进入 0 转移边(没交换前是 1 转移边)的儿子递归。
考虑二进制下 +1,相当于从低位→高位找到第一个 0,将其改为 1,将前面的所有 1 改为 0。
namespace trie {
constint pond = ...;
int nClock, root;
structnode {int trans[2];
int cnt;
int xr;
#define lc trans[0]#define rc trans[1]
} t[pond];
voidupd(int p) {
t[p].cnt = t[t[p].lc].cnt + t[t[p].rc].cnt;
t[p].xr = (t[t[p].lc].xr << 1) ^ (t[t[p].rc].xr << 1) ^ (t[t[p].rc].cnt & 1);
}
voidinsert(int &p, int dep, int x, int opt) {
if (!p) p = ++ nClock;
if (dep == logA) { t[p].cnt += opt; return; }
int v = x >> dep & 1;
insert(t[p].trans[v], dep - 1, x, opt);
upd(p);
}
voidadd_one(int p) {
std::swap(t[p].lc, t[p].rc);
if (t[p].lc) add_one(t[p].lc);
upd(p);
}
}
Trick:CF241B。
AC 自动机
AC 自动机:AC 自动机是一个状态机,形态是 trie。一个起点,若干终点。多模式串的所有前缀,与 AC 自动机上的所有状态一一对应。
trie 图中的普通转移边:从状态 s 指向状态 s′+c,其中 s′ 为 s 的满足 s′+c 存在的最长后缀。
namespace AC {
int nClock = 1;
structnode {int trans[26];
int fail;
} t[N];
voidinsert(char *s) {
int p = 1, len = strlen(s);
for (int i = 0; i < len; i ++) {
int v = s[i] - 'a';
if (!t[p].trans[v]) t[p].trans[v] = ++ nClock;
p = t[p].trans[v];
}
}
voidbuild_fail() {
for (int i = 0; i < 26; i ++) t[0].trans[i] = 1;
t[1].fail = 0;
std::queue<int> q;
q.push(1);
while (q.size()) {
int u = q.front(); q.pop();
for (int i = 0; i < 26; i ++)
if (t[u].trans[i])
t[t[u].trans[i]].fail = t[t[u].fail].trans[i], q.push(t[u].trans[i]);
else
t[u].trans[i] = t[t[u].fail].trans[i];
}
}
}
namespace AC {
int nClock = 1;
structnode {int trans[26];
int fail;
} t[N];
voidinsert(char *s) {
int p = 1, len = strlen(s);
for (int i = 0; i < len; i ++) {
int v = s[i] - 'a';
if (!t[p].trans[v]) t[p].trans[v] = ++ nClock;
p = t[p].trans[v];
}
}
voidbuild_fail() {
for (int i = 0; i < 26; i ++) t[0].trans[i] = 1;
t[1].fail = 0;
std::queue<int> q;
q.push(1);
while (q.size()) {
int u = q.front(); q.pop();
for (int i = 0; i < 26; i ++)
if (t[u].trans[i])
t[t[u].trans[i]].fail = t[t[u].fail].trans[i], q.push(t[u].trans[i]);
else
t[u].trans[i] = t[t[u].fail].trans[i];
}
}
int tot, head[N], ver[N], Next[N];
voidadd_edge(int u, int v) {
ver[++ tot] = v; Next[tot] = head[u]; head[u] = tot;
}
voidbuild_tree() { // fail 树for (int i = 2; i <= nClock; i ++)
add_edge(t[i].fail, i);
}
}
建 trie 图时,当字符集很大时,需要用到 std::map 与可持久化数组建 trie 图。
namespace SGT {
constint pond = ...;
int nClock;
structnode {int lc, rc;
int val;
} t[pond];
voidbuild(int &p, int l, int r) {
p = ++ nClock;
if (l == r) { t[p].val = 1; return; }
int mid = (l + r) >> 1;
build(t[p].lc, l, mid), build(t[p].rc, mid + 1, r);
}
voidinsert(int &p, int q, int l, int r, int x, int val) {
p = ++ nClock, t[p] = t[q];
if (l == r) { t[p].val = val; return; }
int mid = (l + r) >> 1;
if (x <= mid)
insert(t[p].lc, t[q].lc, l, mid, x, val);
else
insert(t[p].rc, t[q].rc, mid + 1, r, x, val);
}
intask(int p, int l, int r, int x) {
if (l == r) return t[p].val;
int mid = (l + r) >> 1;
if (x <= mid)
return ask(t[p].lc, l, mid, x);
elsereturn ask(t[p].rc, mid + 1, r, x);
}
}
namespace AC {
int nClock = 1;
structnode {std::map<int, int> trans;
int fail;
} t[N];
int root[N];
voidinsert(constint &n, int *a) {
int p = 1;
for (int i = 1; i <= n; i ++) {
int v = a[i];
if (!t[p].trans.count(v)) t[p].trans[v] = ++ nClock;
p = t[p].trans[v];
}
}
voidbuild_fail() {
SGT::build(root[0], 1, m);
t[1].fail = 0;
std::queue<int> q;
q.push(1);
while (q.size()) {
int u = q.front(); q.pop();
root[u] = root[t[u].fail];
for (auto it : t[u].trans) {
int c = it.first, v = it.second;
SGT::insert(root[u], root[u], 1, m, c, v);
t[v].fail = SGT::ask(root[t[u].fail], 1, m, c);
}
}
}
}
namespace SA {
int m = 256;
int sa[N], rk[N], height[N];
int cnt[N], id[N], px[N];
int tmp_rk[N];
boolsame(int x, int y, int k) {
int p = x + k <= n ? tmp_rk[x + k] : -1;
int q = y + k <= n ? tmp_rk[y + k] : -1;
return tmp_rk[x] == tmp_rk[y] && p == q;
}
voidbuild() {
for (int i = 1; i <= n; i ++) rk[i] = s[i];
for (int i = 1; i <= n; i ++) cnt[rk[i]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[rk[i]] --] = i;
for (int k = 1, p = 0; k < n; k <<= 1, m = p) {
p = 0;
for (int i = n - k + 1; i <= n; i ++) id[++ p] = i;
for (int i = 1; i <= n; i ++)
if (sa[i] > k) id[++ p] = sa[i] - k;
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[px[i]] --] = id[i];
for (int i = 1; i <= n; i ++) tmp_rk[i] = rk[i];
p = 0;
for (int i = 1; i <= n; i ++) rk[sa[i]] = same(sa[i - 1], sa[i], k) ? p : ++ p;
}
for (int i = 1, h = 0; i <= n; i ++) {
if (h) h --;
while (s[i + h] == s[sa[rk[i] - 1] + h]) h ++;
height[rk[i]] = h;
}
}
}
// 边带权int tot, head[N], ver[N * 2], edge[N * 2], Next[N * 2];
voidadd_edge(int u, int v, int w) {
ver[++ tot] = v; edge[tot] = w; Next[tot] = head[u]; head[u] = tot;
}
int anc[logN + 1][N];
int col[N];
voiddfs(int u, int fu) {
anc[0][u] = fu;
for (int i = 1; i <= logN; i ++) anc[i][u] = anc[i - 1][anc[i - 1][u]];
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i], w = edge[i];
if (v == fu) continue;
col[v] = w;
dfs(v, u);
}
}
namespace SA {
int m = 256;
int sa[N], rk[N], height[N];
int cnt[N], id[N], px[N];
int anc_rk[logN + 1][N];
intget_lcp(int x, int y) {
int cur = 0;
for (int i = logN; i >= 0; i --) {
if (!anc[i][x] || !anc[i][y]) continue;
if (anc_rk[i][y] ^ anc_rk[i][y]) continue;
x = anc[i][x], y = anc[i][y], cur ^= (1 << i);
}
return cur;
}
boolsame(int x, int y, int k) {
int p = anc[k][x] ? anc_rk[k][anc[k][x]] : -1;
int q = anc[k][y] ? anc_rk[k][anc[k][y]] : -1;
return anc_rk[k][x] == anc_rk[k][y] && p == q;
}
voidbuild() {
for (int i = 1; i <= n; i ++) rk[i] = col[i];
for (int i = 1; i <= n; i ++) cnt[rk[i]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[rk[i]] --] = i;
for (int k = 0, p = 0; (1 << k) < n; k ++, m = p) {
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[anc[k][i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) id[cnt[px[i]] --] = i;
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[px[i]] --] = id[i];
for (int i = 1; i <= n; i ++) anc_rk[k][i] = rk[i];
p = 0;
for (int i = 1; i <= n; i ++) rk[sa[i]] = same(sa[i - 1], sa[i], k) ? p : ++ p;
}
for (int i = 1; i <= n; i ++) rk[sa[i]] = i;
for (int i = 2; i <= n; i ++) height[i] = get_lcp(sa[i - 1], sa[i]);
}
}
// 点带权int tot, head[N], ver[N * 2], edge[N * 2], Next[N * 2];
voidadd_edge(int u, int v, int w) {
ver[++ tot] = v; edge[tot] = w; Next[tot] = head[u]; head[u] = tot;
}
int dep[N];
int anc[logN + 1][N];
voiddfs(int u, int fu) {
dep[u] = dep[fu] + 1;
anc[0][u] = fu;
for (int i = 1; i <= logN; i ++) anc[i][u] = anc[i - 1][anc[i - 1][u]];
for (int i = head[u]; i; i = Next[i]) {
int v = ver[i];
if (v == fu) continue;
dfs(v, u);
}
}
namespace SA {
int m = 256;
int sa[N], rk[N], height[N];
int cnt[N], id[N], px[N];
int anc_rk[logN + 1][N];
intget_lcp(int x, int y) {
int cur = 0;
for (int i = logN; i >= 0; i --) {
if (std::min(dep[x], dep[y]) < (1 << i)) continue;
if (anc_rk[i][x] ^ anc_rk[i][y]) continue;
x = anc[i][x], y = anc[i][y], cur ^= (1 << i);
}
return cur;
}
boolsame(int x, int y, int k) {
int p = anc[k][x] ? anc_rk[k][anc[k][x]] : -1;
int q = anc[k][y] ? anc_rk[k][anc[k][y]] : -1;
return anc_rk[k][x] == anc_rk[k][y] && p == q;
}
voidbuild() {
for (int i = 1; i <= n; i ++) rk[i] = s[i];
for (int i = 1; i <= n; i ++) cnt[rk[i]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[rk[i]] --] = i;
for (int k = 0, p = 0; (1 << k) < n; k ++, m = p) {
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[anc[k][i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) id[cnt[px[i]] --] = i;
for (int i = 0; i <= m; i ++) cnt[i] = 0;
for (int i = 1; i <= n; i ++) cnt[px[i] = rk[id[i]]] ++;
for (int i = 1; i <= m; i ++) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i --) sa[cnt[px[i]] --] = id[i];
for (int i = 1; i <= n; i ++) anc_rk[k][i] = rk[i];
p = 0;
for (int i = 1; i <= n; i ++) rk[sa[i]] = same(sa[i - 1], sa[i], k) ? p : ++ p;
}
for (int i = 1; i <= n; i ++) rk[sa[i]] = i;
for (int i = 2; i <= n; i ++) height[i] = get_lcp(sa[i - 1], sa[i]);
}
}
本质不同子串个数:
n(n+1)2−n∑i=2heighti
SAM
SAM 与其他后缀数据结构的关系:
后缀 trie:一个字符串 s 的所有后缀组成的 trie。
后缀 trie 上建 AC 自动机的 fail 指针:表示删去某个状态所表示的串的首字符。
后缀树:后缀 trie 虚树化(仅保留所有后缀在后缀 trie 上的终止节点,及两两 LCA)。
后缀自动机的 parent 树:反串的后缀树。
SAM:SAM 是一个状态自动机,形态是 DAG。一个起点,若干终点。原串的所有子串,与 SAM 上从起点开始、任意点结束的所有路径一一对应、不重不漏。
子串定位(求 s[l:r] 在 SAM 中的对应状态):先定位 s[1:r] 在 SAM 中的对应状态(在构造 SAM 的过程中记录即可),在 parent 树上倍增,找到 maxl 大于等于 r−l+1 且深度最浅的状态。
子串匹配(给定模式串 S 与文本串 T,对每个 r 求最小的 l 使得 T[l:r] 为 S 的子串):采用增量法。维护当前匹配到的子串长度与在 SAM 上的对应状态,当右端点扩展时,不断跳后缀链接,直到跳到起点或存在相应的转移边为止,若存在相应的转移边,则走该相应的转移边。
子 SAM(给定模式串 S 与文本串 T,令 T 在 S[l:r] 中匹配):判断由 S[l:r] 组成的 SAM 是否存在相应的转移边,首先在原 SAM 中就需要存在相应的转移边,其次在原 SAM 中新状态的 endpos 集合需要在 [l+len′−1,r] 中有元素。失配时,应尝试 len′←len′−1 后继续查询,而非直接跳失配指针。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】