并查集的删除操作

有一类删除叫做惰性删除,其特点是,这个点仍然在集合中但不去统计它。
对于并查集,一般来说想要实现删除是有些困难的,不过如果用一些黑科技就会非常简洁。
首先先引入按秩合并并查集的概念。用路径压缩的并查集,复杂度虽然是 O(nα(n)) ,但是会破坏原本的结构,这是比较令人不爽的。
但是朴素的并查集在最坏的情况下查询会被卡成 O(n) ,这是无法接受的慢。所以我们需要让并查集尽量平衡。实现也很简洁:

void uni(int x, int y) {
    int xx = find(x), yy = find(y);
    if(xx == yy) return;
    if(rank[xx] > rank[yy]) fa[yy] = xx;
    else if(rank[xx] < rank[yy]) fa[xx] = yy;
    else ++rank[xx], fa[yy] = xx;
}

其中rank数组维护的是深度,我们尽量将深度小的合并到深度大的上面去,如果深度一样就加一下深度随便合并了。这样树是几乎平衡的,所以查询就是 O(log n) 的。
然后来思考一下如何删除。直接删除固然是不可能的,考虑到我们需要处理其子孙的关系,所以可以建一个虚点。

void del(int x) {
    pid[x] = ++N;
    fa[pid[x]] = pid[x];
    rank[pid[x]] = 1;
}

看上去不是很好懂的样子,其实原理很简单。
用一个动态变化的pid数组来维护编号 i 的实际位置
我们对某个点i进行删除的时候,先将 i 的位置进行修改,修改之后的结果就是这个点自成一个集合。
再考虑原来的点,好像没什么影响,如果是对儿子的操作的话其父亲指向并不会变,同样fa[i]也不会变化,所以整个集合的形态是没有变化的。
唯一有影响的是对 i <script type="math/tex" id="MathJax-Element-400">i</script>进行查找操作的时候会找错集合,所以我们需要对查找函数进行一些修改:

int find(int x) {
    int id = pid[x];
    while(fa[id] != id) id = fa[id];
    return id;
}

当然还有一些配套的操作,比如开一个栈维护加入过哪些集合以实现规模较小的可持久化(如果要实现真正的可持久化请移步主席树),或是加一个vis数组来标志某个数是否被删除。
好像就是这样来着…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值