洛谷 P1196 [NOI2002]银河英雄传说 并查集变形 不仅找根还找最小后代

题目链接:

https://www.luogu.com.cn/problem/P1196

算法:

并查集

一:首先先讲一种并没有ac,而是ac了4个点,TLE了4个点的答案:

       1:为什么要讲,因为总结了几点自己原来会用但是并不能区分两种递归之间区别的并查集找根函数

       先看这段代码

int find(int x)//查根
{
    if(x!=fa[x])return find(fa[x]);
}

int find_son(int x)//查最小后代
{
    if(x!=son[x])return find_son(son[x]);
}

对于一棵退化为链表的树1-->2-->3-->4-->5

在整个代码(即整个并查集运行完),具体输入数据是什么不重要,解释清区别即可

运行:

    for(int i=1;i<=5;i++)
    cout<<fa[i]<<"               "<<son[i]<<endl;

结果是这样的

2               1
3               1
4               2
5               3
5               4

即fa数组存的就是每一个节点的爸爸,不是根节点,son数组存的是这个节点的孩子,不是最小后代

而如果代码写成

int find(int x)//查根
{
    if(x!=fa[x])fa[x]=find(fa[x]);
    return fa[x];
}

int find_son(int x)//查最小后代
{
    if(x!=son[x])son[x]=find_son(son[x]);
    return son[x];
}

运行结果为:

5               1
5               1
5               2
5               3
5               4

区别在于,第二个递归函数把根节点作为每一个节点的父节点递归下来了

当然,如果代码写成第二种,结果也是错误的

 

下边来讲自己为什么TLE了4个点,就是,自己这样做,是一直把树维护成了链表,50000的数据量就超时了

代码如下:

#include <bits/stdc++.h>

using namespace std;
const int maxn=3e4+1;
int fa[maxn],t[maxn],son[maxn],n,x,y;
char ch;

void init(){ for(int i=1;i<maxn;i++){fa[i]=i;son[i]=i;t[i]=1;}}

int find(int x)//查根
{
    if(x!=fa[x])return find(fa[x]);
}

int find_son(int x)//查最小后代
{
    if(x!=son[x])return find_son(son[x]);
}

void unity(int x,int y)//合并,更新t[]数组,t数组代表的是以某一节点为根节点的这棵树(实际上将退化为链表)
//所包含的节点个数,最终也会用它来计算两结点之间的节点数
{
    int r1=find(fa[x]),r2=find_son(son[x]),r3=find(fa[y]),r4=find_son(son[y]);
    //分别找x,y的根,和最小后代
    if(r1!=r3)
    {
       fa[r1]=r4;
       son[r4]=r1;
       //这两句代码是核心,即连接处理,将x的根,接到y的最小后代,即x的根的爸爸是y的最小后代
       //y的最小后代的孩子是x的根

       //更新y所在树中以每一个节点为根的树的节点总数,(其实就是链表长度,因为树都退化为了链表)
       //x所在树的节点为根的树的大小不变
        for(int i=r4;i;i=fa[i])
        {
            t[i]+=t[r1];
            if(i==r3)break;
        }
    }
}

int main()
{
    ios::sync_with_stdio(0);
    cin>>n;
    init();
    for(int i=1;i<=n;i++)
    {
        cin>>ch>>x>>y;
        if(ch=='M'){unity(x,y);}
        else if(ch=='C')
        {
            if(find(x)!=find(y)){printf("%d\n",-1);}
            else {printf("%d\n",abs(t[y]-t[x])-1);}
        }
    }
    return 0;
}

 

参考博客:https://www.luogu.com.cn/blog/santishijie/solution-p1196

思路:

1:对于每个点,分别记录所属链的头结点、该点到头结点的距离以及它所在集合的大小,

2:每次合并将y接在x的尾部,改变y头的权值和所属链的头结点,同时改变x的尾节点

3:每次查找的时候也要维护每个节点的权值

4:每次查询时计算两点的权值差

#include <bits/stdc++.h>

using namespace std;
const int maxn=3e4+1;
int f[maxn],s[maxn],b[maxn],n,x,y,dx,dy;
char ch;

int find(int o)//查找
{
    if(f[o]==o) return o;
    int k=f[o];
    f[o]=find(f[o]);
    s[o]+=s[k];//更新当前节点到根的距离
    b[o]=b[f[o]];//更新所在集合大小
    return f[o];
}

int main()
{
    cin>>n;
    for(int i=1;i<=30000;i++) {f[i]=i;s[i]=0;b[i]=1;}
    for(int i=1;i<=n;i++)
    {
        cin>>ch>>x>>y;
        if(ch=='M')
        {
            dx=find(x),dy=find(y);//查找x,y的根
            f[dx]=dy;//把x放在y后面
            s[dx]+=b[dy];//更新x的根到新的根的距离
            b[dx]+=b[dy],b[dy]=b[dx];//更新集合大小
        }
        if(ch=='C')
        {
            dx=find(x),dy=find(y);
            if(dx!=dy)cout<<-1<<endl;//不在同一个集合中
            else cout<<abs(s[x]-s[y])-1<<endl;//中间战舰的数量等于x到根的距离减y到根的距离减1。
        }
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值