
2.8 字符串
kmp
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
// 匹配
for (int i = 1, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
Trie 树 (字典树、前缀树)
Trie树(前缀树/字典树)是一种树形数据结构,用于高效地存储和检索字符串集合,特别是具有公共前缀的字符串。
- 每个节点代表一个字符串的前缀。
- 根节点为空,子节点表示从根到该节点的路径组成的前缀。
- 插入和查询的时间复杂度为 O(L),其中 L 是字符串长度。
常见应用
- 字符串查询:
- 判断某字符串是否存在。
- 判断某前缀是否存在。
- 单词统计:
- 统计某字符串出现的次数。
- 统计以某前缀开头的字符串数量。
- 最长公共前缀问题。
- 字典序排序:从Trie树中提取所有字符串,可按字典序输出。
- 自动补全:根据前缀匹配可能的候选词。
- 拼词游戏:判断单词拼接的可能性。
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
void insert(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) son[p][u] = ++ idx;
p = son[p][u];
}
cnt[p] ++ ;
}
// 查询字符串出现的次数
int query(char *str)
{
int p = 0;
for (int i = 0; str[i]; i ++ )
{
int u = str[i] - 'a';
if (!son[p][u]) return 0;
p = son[p][u];
}
return cnt[p];
}
线段树
线段树(点更新)
/*
实现功能:
1. 建立线段树,支持查询区间最大值和区间和。
2. 支持点更新操作(修改单个元素的值)。
适用题目:
- 查询区间最大值:如动态维护区间中的最大元素。
- 查询区间和:如 LeetCode 307. 区域和检索 - 数组可修改。
*/
struct node {
int left, right; // 该节点管理的区间 [left, right]
int max, sum; // 该区间的最大值与和
};
node tree[maxn << 2]; // 线段树数组
int a[maxn]; // 输入数组
int n; // 数组长度
// 构建线段树
void build(int m, int l, int r) { // m:当前树节点编号,[l, r]:该节点管理的区间
tree[m].left = l;
tree[m].right = r;
if (l == r) { // 叶子节点
tree[m].max = a[l];
tree[m].sum = a[l];
return;
}
int mid = (l + r) >> 1; // 中点
build(m << 1, l, mid); // 递归构建左子树
build(m << 1 | 1, mid + 1, r); // 递归构建右子树
// 合并左右子区间
tree[m].max = max(tree[m << 1].max, tree[m << 1 | 1].max);
tree[m].sum = tree[m << 1].sum + tree[m << 1 | 1].sum;
}
// 点更新操作:修改位置 a 的值为原值加 val
void update(int m, int a, int val) {
if (tree[m].left == a && tree[m].right == a) { // 定位到叶子节点
tree[m].max += val;
tree[m].sum += val;
return;
}
int mid = (tree[m].left + tree[m].right) >> 1;
if (a <= mid)
update(m << 1, a, val); // 更新左子树
else
update(m << 1 | 1, a, val); // 更新右子树
// 更新当前节点的信息
tree[m].max = max(tree[m << 1].max, tree[m << 1 | 1].max);
tree[m].sum = tree[m << 1].sum + tree[m << 1 | 1].sum;
}
// 查询区间 [l, r] 的和
int querySum(int m, int l, int r) {
if (l == tree[m].left && r == tree[m].right) // 完全覆盖
return tree[m].sum;
int mid = (tree[m].left + tree[m].right) >> 1;
if (r <= mid)
return querySum(m << 1, l, r); // 查询左子树
else if (l > mid)
return querySum(m << 1 | 1, l, r); // 查询右子树
return querySum(m << 1, l, mid) + querySum(m << 1 | 1, mid + 1, r); // 合并结果
}
// 查询区间 [l, r] 的最大值
int queryMax(int m, int l, int r) {
if (l == tree[m].left && r == tree[m].right) // 完全覆盖
return tree[m].max;
int mid = (tree[m].left + tree[m].right) >> 1;
if (r <= mid)
return queryMax(m << 1, l, r); // 查询左子树
else if (l > mid)
return queryMax(m << 1 | 1, l, r); // 查询右子树
return max(queryMax(m << 1, l, mid), queryMax(m << 1 | 1, mid + 1, r)); // 合并结果
}
线段树(区间更新)
/*
实现功能:
1. 构建线段树,支持区间更新操作(区间加)。
2. 支持区间查询操作。
适用题目:
- 区间加操作 + 区间和查询:如 LeetCode 307、HDU 1698。
- 动态维护区间信息,解决区间修改类问题。
*/
struct node {
ll l, r; // 区间范围
ll addv, sum; // addv 表示延迟标记(区间加值),sum 表示区间和
};
node tree[maxn << 2]; // 线段树数组
// 更新当前节点信息
void maintain(int id) {
tree[id].sum = tree[id << 1].sum + tree[id << 1 | 1].sum; // 合并左右子树的和
}
// 将延迟标记下推到子节点
void pushdown(int id) {
if (tree[id].addv) {
int tmp = tree[id].addv;
// 更新子节点的延迟标记和和
tree[id << 1].addv += tmp;
tree[id << 1 | 1].addv += tmp;
tree[id << 1].sum += (tree[id << 1].r - tree[id << 1].l + 1) * tmp;
tree[id << 1 | 1].sum += (tree[id << 1 | 1].r - tree[id << 1 | 1].l + 1) * tmp;
tree[id].addv = 0; // 清除当前节点的标记
}
}
// 构建线段树
void build(int id, ll l, ll r) {
tree[id].l = l;
tree[id].r = r;
tree[id].addv = 0;
tree[id].sum = 0;
if (l == r) return; // 叶子节点
ll mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
maintain(id);
}
// 区间更新:将 [l, r] 区间内的每个元素加上 val
void updateAdd(int id, ll l, ll r, ll val) {
if (tree[id].l >= l && tree[id].r <= r) { // 完全覆盖
tree[id].addv += val;
tree[id].sum += (tree[id].r - tree[id].l + 1) * val;
return;
}
pushdown(id); // 将标记下推
ll mid = (tree[id].l + tree[id].r) >> 1;
if (l <= mid) updateAdd(id << 1, l, r, val); // 更新左子树
if (mid < r) updateAdd(id << 1 | 1, l, r, val); // 更新右子树
maintain(id); // 更新当前节点信息
}
// 区间查询:求 [l, r] 区间的和
void query(int id, ll l, ll r, ll &anssum) {
if (tree[id].l >= l && tree[id].r <= r) { // 完全覆盖
anssum += tree[id].sum;
return;
}
pushdown(id); // 将标记下推
ll mid = (tree[id].l + tree[id].r) >> 1;
if (l <= mid) query(id << 1, l, r, anssum); // 查询左子树
if (mid < r) query(id << 1 | 1, l, r, anssum); // 查询右子树
}
树状数组
/*
实现功能:
1. 支持前缀和查询。
2. 支持单点更新。
适用题目:
- 区间求和问题:如 LeetCode 307。
- 动态维护前缀和类问题。
*/
int a[maxn]; // 树状数组存储的前缀和
int n; // 数组长度
// 计算 lowbit
int lowbit(int x) { return x & (-x); }
// 单点更新:位置 t 加上 d
void insert(int t, int d) {
while (t <= n) {
a[t] += d;
t += lowbit(t);
}
}
// 查询前缀和:1 到 t 的和
ll getSum(int t) {
ll sum = 0;
while (t > 0) {
sum += a[t];
t -= lowbit(t);
}
return sum;
}