题目链接:
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;
结果是这样的
扫描二维码关注公众号,回复:
9051574 查看本文章
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;
}