链接
题目描述
给出两种指令,一种以M开头,表示将a,b集合合并(并且是a的整个集合接到b的整个集合的末尾)一种是C开头,表示询问a,b是否在同一集合(不在输出-1),并且同一集合的要求出a,b之间的数的个数
样例输入
4
M 2 3
C 1 2
M 2 4
C 4 2
样例输出
-1
1
思路
emm,一开始想着要记录集合末端,后面发现很难处理数与数之间的距离,然后我就在冥思苦想啊,冥思苦想,然后就看题解 查资料,就学了个带权并查集(好家伙我啥都不会
那这道题就可以这么做:
设
t
o
t
tot
tot数组为集合的数的个数,
d
i
s
dis
dis表示从当前点到集合的头的距离
因为这道题的集合合并后是一条链,那么查询操作里的求距离就可以转化为
∣
d
i
s
[
a
]
−
d
i
s
[
b
]
∣
−
1
\left|{dis[a]}- {dis[b]} \right| - 1
∣dis[a]−dis[b]∣−1(因为求的是数与数之间的距离,而不是边,所以要-1)
1.合并操作就正常合并集合,然后更新一下
t
o
t
tot
tot和
d
i
s
dis
dis的值(tot 父亲就是父亲加上儿子,儿子继承父亲
dis就是儿子 = dis儿子 + tot父亲
2.求集合头的操作,可以在路径上不断更新当前询问到的点的tot和dis值(tot直接继承,dis 是dis儿子 += dis父亲
3.查询操作就直接判断,然后直接按照上面给的式子去求就好了
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
char c;
int fa[3000005], dis[3000005], tot[3000005];
int find(int x)
{
if(fa[x] == x) return x;
int faa = fa[x];
fa[x] = find(fa[x]);
dis[x] += dis[faa];//更新距离集合头的长度
tot[x] = tot[faa];//继承集合大小
return fa[x];
}
void connect(int a, int b)
{
int x = find(a), y = find(b);
fa[x] = y;
dis[x] = dis[y] + tot[y];//新集合离集合头的距离就是原本这个集合头的距离 + 集合的大小
tot[y] += tot[x];//更新集合大小
}//合并集合
int main()
{
scanf("%d", &n);
c = getchar();
for(int i = 1; i <= 300005; ++i)
fa[i] = i, tot[i] = 1;
for(int i = 1; i <= n; ++i)
{
int a, b;
c = getchar();
while(c != 'M' && c != 'C') c = getchar();
scanf("%d%d", &a, &b);
if(c == 'M')
connect(a, b);
else {
int x = find(a), y = find(b);
if(x == y)
printf("%d\n", abs(dis[a] - dis[b]) - 1);//求距离
else printf("-1\n");
}
}
return 0;
}