题目地址:
http://acm.hdu.edu.cn/showproblem.php?pid=3635
题目类型: 并查集
题目:
Five hundred years later, the number of dragon balls will increase unexpectedly, so it's too difficult for Monkey King(WuKong) to gather all of the dragon balls together.
样例输入:
2 3 3 T 1 2 T 3 2 Q 2 3 4 T 1 2 Q 1 T 1 3 Q 1
样例输出:
Case 1: 2 3 0 Case 2: 2 2 1 3 3 2
题目大意:
有N的龙珠,编号为1~N,开始时分别分布在编号为1~N的城市。
然后如果是输入T a b,那么进行移动, 把编号a的龙珠所在的城市的所有龙珠移动到编号b龙珠所在的城市。
输入Q a, 那么输出a龙珠所在的城市, a龙珠所在城市有的龙珠的个数, 以及a龙珠移动的次数。
思路与总结:
首先,感觉这题确实是一道很好的并查集题目,可以加深对并查集和压缩路径的理解。
做这题有点略纠结,WA了很多次,说明我对并查集的理解还是不够。
求一个龙珠在那个城市很好办, 只需要find(x),找到x的根结点,即是x所在的城市
求一个城市有多少个龙珠也很好办, 只需要加上一个rank数组,用来表示并查集的秩,及树的高度,就是那个城市的龙珠的数量。
而求一个龙珠移动的次数,比较难搞,也正式我纠结之处。
首先,当一座城市的龙珠全部移动到另一个城市之后,那么这个城市的龙珠数量变为0, 也就是说没有龙珠是在这个城市了,那么之后都不会再有龙珠移动到
这个空城市。因为T a x,必须是移动到龙珠x所在的城市,而现在已经没有龙珠落在空城市了,对于任意龙珠x,都不会是落在空城市。最后可以得出结论,一个
城市只能移动一次。
开一个数组num表示各个求的移动次数。
每次移动时, 设是T a b, 那么b所在城市(即b的根结点)的最初始的球, 是这个球的第一次移动, 这时给初始球的移动次数置为1。
光光是这样做的话,还不够。因为这样只增加了根结点的初始那个球的次数,还有其他球在这个跟结点的次数还没有加。
别急, 这个这个步骤可以留给路径压缩的时候做。
还没进行路径压缩时,它的父亲是以前的那个跟结点,而这时候,之前欠下来的债也还了,让它的移动次数加上它所有祖宗的移动次数(这个是关键,也比较
难理解)。
最后,它只想了新结点。 次数也跟新了。
进行路径压缩之后,每个球的父亲结点就是它所在的城市。
#include<iostream> #include<cstdio> #include<cstring> #define MAXN 10002 using namespace std; int N, Q, father[MAXN], rank[MAXN], num[MAXN]; void init(){ for(int i=1; i<=N; ++i){ father[i] = i, rank[i] = 1, num[i]=0; } } int find(int x){ if(x==father[x]) return x; int t=father[x]; father[x] = find(father[x]); num[x] += num[t]; return father[x]; } void Union(int x,int y){ int a=find(x); int b=find(y); if(a!=b){ father[a] = b; rank[b] += rank[a]; num[a] = 1; // 这是球a第一次移动 } } int main(){ #ifdef LOCAL freopen("input.txt","r",stdin); #endif int T,a,b,k,cas=1; char cmd[2]; scanf("%d", &T); while(T--){ printf("Case %d:\n",cas++); scanf("%d %d", &N,&Q); init(); for(int i=0; i<Q; ++i){ scanf("%s", cmd); if(cmd[0]=='T'){ scanf("%d %d", &a, &b); Union(a, b); } else{ scanf("%d", &k); int x = find(k); int cnt=0; printf("%d %d %d\n", x, rank[x],num[k]); } } } return 0; }