常用代码模板(二)

并查集

(1) 朴素并查集:
int p[N]; // 存储每个节点的祖宗节点,初始时每个节点的祖宗是它自己

// 查找操作,带路径压缩
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); // 如果x不是自己所在集合的祖宗,则递归地找到祖宗
    return p[x]; // 返回x的祖宗节点
}

// 初始化操作
for (int i = 1; i <= n; i++) p[i] = i; // 每个节点的初始祖宗节点是它自己

// 合并操作,将a和b所在的集合合并
p[find(a)] = find(b); // 找到a和b的祖宗节点,并将它们合并
(2) 维护size的并查集:
int p[N], size[N]; // p[]存储每个节点的祖宗节点,size[]存储每个祖宗节点所在集合的大小

// 查找操作,带路径压缩
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]); // 如果x不是祖宗节点,递归地找到祖宗
    return p[x]; // 返回x的祖宗节点
}

// 初始化操作
for (int i = 1; i <= n; i++) {
    p[i] = i; // 每个节点的初始祖宗节点是它自己
    size[i] = 1; // 每个节点所在集合的初始大小为1
}

// 合并操作,将a和b所在的集合合并,并更新大小
size[find(b)] += size[find(a)]; // 将a和b所在集合的大小相加,更新合并后集合的大小
p[find(a)] = find(b); // 将a的祖宗节点指向b的祖宗节点,完成两个集合的合并
(3) 维护到祖宗节点距离的并查集:
int p[N], d[N]; // p[]存储每个节点的祖宗节点,d[]存储每个节点到其祖宗节点的距离

// 查找操作,带路径压缩和距离更新
int find(int x) {
    if (p[x] != x) {
        int u = find(p[x]); // 递归地找到p[x]的祖宗节点
        d[x] += d[p[x]]; // 更新x到其祖宗节点的距离,等于x到p[x]的距离加上p[x]到其祖宗节点的距离
        p[x] = u; // 更新x的祖宗节点
    }
    return p[x]; // 返回x的祖宗节点
}

// 初始化操作
for (int i = 1; i <= n; i++) {
    p[i] = i; // 每个节点的初始祖宗节点是它自己
    d[i] = 0; // 每个节点到其祖宗节点的初始距离是0
}

// 合并操作,将a和b所在的集合合并,并更新距离
p[find(a)] = find(b); // 找到a和b的祖宗节点,并将它们合并
// 根据具体问题,初始化find(a)的偏移量
// d[find(a)] = distance; // 这行代码需要根据实际情况添加到合并操作中,用于设置合并后a所在集合的偏移量

// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;

// 交换两个点在堆中的值及其映射关系
void heap_swap(int a, int b)
{
    swap(ph[hp[a]], ph[hp[b]]); // 交换第a个和第b个插入元素在ph数组中存储的位置
    swap(hp[a], hp[b]); // 交换hp[a]和hp[b]的值,更新映射关系
    swap(h[a], h[b]); // 交换堆中a和b位置的元素
}

// 堆下沉操作,用于维护最小堆性质
void down(int u)
{
    int t = u; // t暂存u节点的值
    // 如果左儿子存在,并且左儿子的值小于t的值,则更新t为左儿子的索引
    if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;
    // 如果右儿子存在,并且右儿子的值小于t的值,则更新t为右儿子的索引
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    // 如果t已经更新,说明u节点的值不是其子树的最小值,需要交换并继续下沉
    if (u != t)
    {
        heap_swap(u, t); // 交换u和t在堆中的位置及其映射关系
        down(t); // 递归地对t进行下沉操作
    }
}

// 堆上浮操作,用于维护最小堆性质
void up(int u)
{
    // 只要父节点存在,并且u节点的值小于父节点的值,就继续上浮
    while (u / 2 && h[u] < h[u / 2])
    {
        heap_swap(u, u / 2); // 交换u和其父节点在堆中的位置及其映射关系
        u >>= 1; // 更新u为父节点的索引,右移一位相当于除以2
    }
}

// O(n)时间复杂度建堆,从最后一个非叶子节点开始,向上进行下沉操作
for (int i = n / 2; i; i--) down(i);

一般哈希

(1) 拉链法
int h[N], e[N], ne[N], idx;
// h[N] 存储哈希表的散列数组,每个元素指向一个链表的头结点
// e[N] 存储每个链表中存储的元素值
// ne[N] 存储每个链表中元素的next指针,指向同一链表的下一个节点
// idx 记录当前插入的元素索引

// 向哈希表中插入一个数x
void insert(int x) {
    int k = (x % N + N) % N; // 计算哈希值,并通过模N保证索引在数组范围内
    e[idx] = x; // 将元素值x存储在e数组的idx位置
    ne[idx] = h[k]; // 将当前散列表k索引位置的头结点指针赋给idx位置的ne,形成链表
    h[k] = idx++; // 更新散列表k索引位置的头结点指针为idx,并将idx递增
}

// 在哈希表中查询某个数是否存在
bool find(int x) {
    int k = (x % N + N) % N; // 计算哈希值
    for (int i = h[k]; i != -1; i = ne[i]) // 遍历散列表k索引位置的链表
        if (e[i] == x) // 如果找到元素x
            return true; // 返回true

    return false; // 如果链表中没有找到x,返回false
}
(2) 开放寻址法
int h[N];
// h[N] 存储哈希表的散列数组

// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x) {
    int t = (x % N + N) % N; // 计算哈希值
    while (h[t] != -1 && h[t] != x) // 循环直到找到一个空位或者找到x
    {
        t++; // 线性探测,移动到下一个位置
        if (t == N) t = 0; // 如果到达数组末尾,循环回到开头
    }
    return t; // 返回x的下标或者x应该插入的位置
}

字符串哈希 

typedef unsigned long long ULL;
// 定义一个类型别名ULL,代表无符号长长整型,用于存储大数

ULL h[N], p[N]; // h数组存储字符串前k个字符的哈希值
// p数组存储P的k次幂模2^64的结果
// 注意:这里的P应该是一个常量,代表进制基数,经验值131或13331

// 初始化操作
p[0] = 1; // p[0]初始化为1,因为任何数的0次幂都是1
for (int i = 1; i <= n; i++)
{
    // 计算字符串前i个字符的哈希值
    h[i] = h[i - 1] * P + str[i]; // 哈希值h[i]是前一个哈希值h[i-1]乘以P再加上当前字符的值str[i]
    // 计算P的i次幂模2^64的结果
    p[i] = p[i - 1] * P; // p[i]是p[i-1]乘以P的结果
}

// 计算子串str[l ~ r]的哈希值
ULL get(int l, int r)
{
    // 利用之前计算的哈希值h[r]和h[l-1],以及p数组来避免大数乘法
    // h[r] - h[l - 1] * p[r - l + 1] 计算子串的哈希值
    // h[l-1] * p[r - l + 1]是将h[l-1]乘以P的(r-l+1)次幂,即乘以子串长度+1次P
    // 这样,h[r] - 这个结果就等于子串str[l ~ r]的哈希值
    return h[r] - h[l - 1] * p[r - l + 1];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值