算法模板4:字符串+字典树+线段树


蛐神镇楼
在这里插入图片描述

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树(前缀树/字典树)是一种树形数据结构,用于高效地存储和检索字符串集合,特别是具有公共前缀的字符串。

  1. 每个节点代表一个字符串的前缀。
  2. 根节点为空,子节点表示从根到该节点的路径组成的前缀。
  3. 插入和查询的时间复杂度为 O(L),其中 L 是字符串长度。
常见应用
  1. 字符串查询
    • 判断某字符串是否存在。
    • 判断某前缀是否存在。
  2. 单词统计
    • 统计某字符串出现的次数。
    • 统计以某前缀开头的字符串数量。
  3. 最长公共前缀问题。
  4. 字典序排序:从Trie树中提取所有字符串,可按字典序输出。
  5. 自动补全:根据前缀匹配可能的候选词。
  6. 拼词游戏:判断单词拼接的可能性。
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梓仁沐白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值