启发式合并


类似并查集里面的按秩合并

定义

一开始是每一个数单独一个集合每一次是将某一个集合里面所有元素,合并到另外一个集合里
如果用暴力来进行合并的话,假设每一次合并都是O(op)的时间复杂度,然后按照最坏的情况,一共有n个,第一个合并到第二个里面,第二个再合并到第三个里面,以此类推,然后这样的话,第一次操作一个数,第二次操作两个数,以此类推,一直到最后一个需要操作n-1位数,这样总共的时间复杂度就是O( n 2 n^2 n2op)时间复杂度较高
然后我们在合并的时候,可以考虑做一步优化,每一次合并其实本质上是将两个集合合并,每次合并加一个启发式的操作,就是每次将一个元素少的集合合并到元素多的集合里面,这样我们就可以把时间复杂度降到O(nlogn * op)

为什么呢?
我们可以从另外一个角度来看,如果我们直接去算每个集合里有多少个元素,每一次合并时间复杂度是O(op)总共是O(n * op)的时间复杂度,这样算比较难算

我们来观察每个元素对最终计算量的贡献是多少,每个元素对于最终计算量的贡献其实就是看一下这个元素被合并多少次,那么这个元素被合并多少次取决于这个元素所在的集合被合并多少次,也就是取决于这个元素每次所在的集合被合并的次数

然后我们看一下,每个元素每次所在的集合最多会被合并多少次
由于我们每一次是把元素较少的集合合并到元素较多的集合里面,因此对于每一个元素,如果这个元素所在的集合每合并一次,它所在集合的元素数量就至少会乘2,一共有n个元素,所以每个元素最多就合并logn次, 所以最多合并的次数就是nlogn * op,

普通启发式合并

梦幻布丁

题意就是说,有一堆布丁,每个布丁有一个颜色,然后有一个操作,每次能让相同颜色的布丁换个颜色,问连续颜色的布丁有多少段

这个题目可以看成一个集合合并,就是每一个颜色看成一个集合

在这里插入图片描述
首先就是要把颜色一一对应,如上图

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define endl '\n'
const int N = 1e5 + 10, M = 1e6 + 10;
int h[M], e[N], ne[N], idx;
int n, m, a, b, c;
int sz[M], p[M], color[N];//sz代表每种颜色的集合大小,p[]是代表每种颜色对应哪个编号
int ans;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
    sz[b] ++;
}//h[a]存储的是最后一个挂在链表a上的节点编号,ne[idx]则是存的上一个

void merge(int &x, int &y)
{
    if(x == y) return;
    if(sz[x] > sz[y]) swap(x, y);
    
    for (int i = h[x]; ~i; i = ne[i])
    {
        int j = e[i];
        ans -= (color[j - 1] == y) + (color[j + 1] == y);
    }
    for (int i = h[x]; ~i; i = ne[i])
    {
        int j = e[i];
        color[j] = y;
        if (ne[i] == -1)
        {
            ne[i] = h[y];//将x的最后一个节点的下一个连到y颜色的第一个节点,将两个串合并
            h[y] = h[x];//将y颜色的头节点连接到x的第一个节点上
            break;
        }
    }
    h[x] = -1;
    sz[y] += sz[x], sz[x] = 0;
}
int main()
{
    
    memset(h, -1, sizeof h);
    // puts("!!!!!!!!!!");
    scanf("%d%d", &n, &m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d", &color[i]);
        if (color[i] != color[i - 1]) ans ++ ;
        add(color[i], i);
    }
    
    for(int i=1; i < M; ++i) p[i] = i;

    while(m--)
    {
        scanf("%d", &c);
        if(c == 2) cout << ans << endl;
        else
        {
            scanf("%d%d", &a, &b);
            merge(p[a], p[b]);
        }
    }
    return 0;
}

树上启发式合并

例题

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值