AcWing 238. 银河英雄传说(并查集 维护树大小、节点与祖宗节点的距离)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
自认为不能将本题的各种细节和注意点讲得尽善尽美,因此将一位dalao的题解拖了过来,我的任务就是将代码写好,并稍微更改一下排版,使之阅读体验更佳。

题意:

T 条指令,指令格式分为以下两种:

M i j:让第 i 号战舰所在列的全部战舰保持原有顺序,接在第 j 号战舰所在列的尾部。(_union
C i j:询问第 i 号战舰与第 j 号战舰当前是否处于同一列中if(find(i)==find(j))),如果在同一列中,它们之间间隔了多少艘战舰。如果不在同一列,则输出“-1”。

思路:

以前我们使用并查集都只是用来维护元素是否在一个集合中,通过这一题才了解到并查集的功能强大之处在于充分利用“边”。如果只用并查集来维护元素是否在一个集合内,其实根本没用到每个集合(树)里的边。学会充分利用边之后,并查集就会变成一个更强大的数据结构。

对于这一题,我们使用了边,规定每个节点与父节点的边的边权为1。这样节点路径上的边权之和就为树根到当前节点的距离

那么,如果执行第二条指令查询ij,若属于同一集合只需输出abs((x到树根的距离) - (y到树根的距离))-1即可。

那么令数组d[x]表示x到父节点p[x]的距离,在查询也就是找树根的过程中顺带d[]更新

一、查询操作:find

int find(int x)
{
        if(p[x]!=x)
        {
                int root = find(p[x]);
                d[x]+=d[p[x]];//用dfs思想理解,先达到最底层,然后回溯时更新
                p[x] = root;//路径压缩
        }
        return p[x];
}

这样,就可以保证每次用到d[x]时,d[x]表示为x到树根的距离。

这里有个易错点,就是有同学可能会先更新d[x],再p[x]=find(p[x])。这样是错的。
因为显然我们要像搜索一样先递归到最底层,然后回溯的时候更新。如果像你那样写,父节点还没更新呢,你就用父节点的信息更新子节点,显然就错了嘛。

至此,查询操作已经完成了。


接下来就是:二、合并操作:_union

假设你将a集合接到b集合后边,实际上就是两棵树相接,思考一下,合并操作对这两棵树有什么影响。

对于a这棵树:每一个节点的d[]都应该加上b这棵树的大小siz[pb],因为是将a接在b屁股后面嘛。
对于b这棵树:节点的d[]不变,只不过合并后它的大小siz[pb]要加上a的大小siz[pa]

pa = find(a), pb = find(b)

我们可以写出如下代码:

void _union(int a, int b)
{
        int pa = find(a), pb = find(b);
        if(pa!=pb)
        {
                d[pa] = siz[pb];
                siz[pb]+=siz[pa];
                p[pa] = pb;
        }
}

关于这个代码:

①我们发现之前都没提到siz数组,这很好办,在上面_union函数之中的find查询时跟d[]一起维护更新即可。

强调一下_union函数里对于d[pa]的更新d[pa] = siz[pb];

其实,这是一个懒标记。

按理说,a里头每一个节点的d[]都应该加上siz[pb](别忘了现在是a树合并到b上,结合d[]的定义很容易得出这个结论),那都要遍历更新一遍的话复杂度不就退化成O(n)了嘛。那么我只需更新根节点的信息。然后在查询的时候,刚才没有被更新的节点再用根节点的信息来更新自己

这样就可以保证用到才更新,相当于线段树里的懒标记。

时间复杂度:

并查集操作接近 O(1)

总时间复杂度O(nlogn)

代码:

#include<bits/stdc++.h>

using namespace std;
int t;
const int N = 3e4+10;
int p[N], d[N], siz[N];

void init(int n)
{
        for(int i=1;i<n;++i) p[i] = i, siz[i] = 1;
}

int find(int x)
{
        if(p[x]!=x)
        {
                int root = find(p[x]);
                d[x]+=d[p[x]];
                p[x] = root;
        }
        return p[x];
}

void _union(int a, int b)
{
        int pa  = find(a), pb = find(b);
        if(pa!=pb)
        {
                d[pa] = siz[pb];
                siz[pb]+=siz[pa];
                p[pa] = pb;
        }
}

int main()
{
        cin>>t;
        init(N);
        while(t--)
        {
                char op[2]; int ii, jj;
                scanf("%s%d%d",op, &ii, &jj);
                if(op[0]=='M') _union(ii, jj);
                else
                {
                        int pii = find(ii), pjj = find(jj);
                        if(pii==pjj) printf("%d\n", max(0, abs(d[ii] - d[jj])-1));
                        else printf("-1\n");
                }
        }

        return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值