转自:https://www.cnblogs.com/yinbiao/p/9464390.html
题目: http://www.fjutacm.com/Contest.jsp?cid=870#P3
这题真的搞得我头痛,可能是我对并查集还没有深刻的理解吧
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.
五百年后,龙珠的数量会出乎意料地增加,所以悟空很难把所有的龙珠聚集在一起。
His country has N cities and there are exactly N dragon balls in the world. At first, for the ith dragon ball, the sacred dragon will puts it in the ith city. Through long years, some cities' dragon ball(s) would be transported to other cities. To save physical strength WuKong plans to take Flying Nimbus Cloud, a magical flying cloud to gather dragon balls.
他的国家有N个城市,世界上正好有N个龙珠。首先,为了第i个龙珠,神圣的龙会把它放在第i个城市。经过漫长的岁月,一些城市的龙珠会被运送到其他城市。为了节省体力,悟空计划乘飞天云,一种神奇的飞天云来收集龙珠。
Every time WuKong will collect the information of one dragon ball, he will ask you the information of that ball. You must tell him which city the ball is located and how many dragon balls are there in that city, you also need to tell him how many times the ball has been transported so far.
每次悟空收集一个龙珠的信息,他都会问你那个龙珠的信息。你必须告诉他球在哪个城市,在那个城市有多少个龙珠,你还需要告诉他球已经被运送了多少次。
For each case, the first line contains two integers: N and Q (2 < N <= 10000 , 2 < Q <= 10000).
Each of the following Q lines contains either a fact or a question as the follow format:
输入的第一行是一个正整数T(0 < T <= 100)。
对于每种情况,第一行包含两个整数:N和Q (2 < N <= 10000, 2 < Q <= 10000)。
下列Q行中的每一行都包含一个事实或一个问题,其格式如下:
T A B : All the dragon balls which are in the same city with A have been transported to the city the Bth ball in. You can assume that the two cities are different.
Q A : WuKong want to know X (the id of the city Ath ball is in), Y (the count of balls in Xth city) and Z (the tranporting times of the Ath ball). (1 <= A, B <= N)
3 3
T 1 2
T 3 2
Q 2
3 4
T 1 2
Q 1
T 1 3
Q 1
2 3 0
Case 2:
2 2 1
3 3 2
int find(int x) // 可将递归分为 两个过程 { int a = x; while (p[x] != x) // 搜索的过程 x = p[x]; while (a != x) // 回溯的过程 { int t = p[a]; p[a] = x; a = t; } return x; } int Find(int x) { if (p[x] != x) p[x] = Find(p[x]); return p[x]; }
find 函数可以要实现两个功能 ① 找到根结点 ② 压缩路径
函数实现可以有两种方法 ① 循环 ② 递归
就是在做这个时,我第一次想到,可以将递归分为两部分 ① 调用自己之前的代码 ② 调用自己之后的代码
愚以为,第一部分 是 搜索 实现找到 根结点 的功能 ;第二部分是 回溯 实现 压缩路径 的功能
我觉得这个应该可以推广到所有递归(然而现在我递归做到的题目不够多,不知道有没有例外 ( 。ớ ₃ờ) )
想法一,之前打算构造一个 mov 数组,在 join 函数连接两个点的时候,让移动的那个城市 id ,mov[id]++,
后发现 当别的点移动时,是会带动集合的点也跟着移动的,这样子,其他点就没有记录到
想法二,还是在 join 函数,连接两个点之前,构造一个函数,让所有指向这个点的 点的mov++, 结果超时了
超时代码:
void fun(int x) { for (int i = 0; i <= n; i++) { if (p[i] == x) mov[i]++; } } void join(int x, int y) { x = find(x), y = find(y); if (x == y) return; fun(x); // p[x] = y; num[y] += num[x]; }
想法三,在想法一的基础上,加一个函数,在查询时,直接让 x 的所有父节点的mov[] 加起来,结果 wa, 原因:忘记被压缩路径了,找不回去了
错误代码:
int move(int x) { int sum = 0, a = x; while (p[x] != x) { sum += mov[x]; x = p[x]; } sum += mov[x]; return sum; } void join(int x, int y) { x = find(x), y = find(y); if (x == y) return; p[x] = y; num[y] += num[x]; mov[x]++; } if (str[0] == 'Q') { scanf("%d", &x); int k = find(x); printf("%d %d %d ", k, num[k], mov[x]); }
所以,我就直接百度了,๑乛◡乛๑ (●´∀`●)
是基于我对想法一,利用 find 的回溯对其他点进行处理
说实话,由于没解释我这个代码直接看了一天,才理解,现在已然是在熬夜了 (@U▽U@)
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #define N 10005 int p[N], num[N], mov[N]; int n, m; int find(int x) { if (p[x] != x) { int t = p[x]; p[x] = find(p[x]); mov[x] += mov[t]; // 回溯时 从根节点加回去的,但是,是 子节点+=父节点 } return p[x]; } void join(int x, int y) { x = find(x), y = find(y); if (x == y) return; p[x] = y; num[y] += num[x]; mov[x]++; } int main(void) { int t; scanf("%d", &t); int ci = 0; while (t--) { scanf("%d%d", &n, &m); for (int i = 0; i <= n; i++) { p[i] = i; num[i] = 1; mov[i] = 0; } printf("Case %d: ", ++ci); char str[10]; while (m--) { scanf("%s", str); int x, y; if (str[0] == 'T') { scanf("%d%d", &x, &y); join(x, y); } if (str[0] == 'Q') { scanf("%d", &x); int k = find(x); printf("%d %d %d ", k, num[k], mov[k]); } } } system("pause"); return 0; }
还是画图比较好理解:
若现在假设 T 3 5 且看图
那么, join 之后,会变成什么呢?且看:
注意到了吗,压缩路径是由延迟的,不是你添加了一个点,他就马上压缩,而是在你连接一个新的点之后,
确定了 根结点,才压缩的,(这也是我做这题时才发现的)
而且,在回溯累加 mov[] 数组的时候,是没有把根节点算上去的,这样子他算的就只有原来那个集合了
不信,接着看。
接下来有两种情况:
① 询问 3 或 4 或 5 或 1,分别得到 mov[3]+mov[1],mov[2]+mov[1],mov[4]+mov[1],mov[1],
然后查询之后路径就被压缩了,若是第二次查询 3 或 4 或 5 或 1,只会得到 mov[3],mov[4],mov[5],mov[1]
② 或者不询问,接着移动那么,就会这样:
T 3 6
join 之后就会:(抱歉,这里有点小错误,由于只是 find(3),find(6),所以,被压缩的应该之后,3->1->5, 而 find(6) 深度为1 就没有压缩了,应该是第二张图,这是之后再来看才发现的 )
正确图片:
递归过程:
x=3; -> t=1 -> p[x]=find(p[x])=5 -> mox[x]+=mov[t] 即 mov[3]+=mov[1]=2; 结束
周而复始。
我能说什么,简直天秀,真的,凡是能想出用递归解决问题都是大佬啊,而且这个直接利用原来就有的递归,简直了ヾ(o◕∀◕)ノ
最后总结一下吧,总体思路应该是和我对的想法3 一样,只是我因为被压缩路径了,不知道如何处理,而这个是利用递归,在回溯时候累加,
因为路径上的每个点都是与前面路径有关,所以每个点必须把前面所有点累加起来,这个用递归就挺简单,但若用循环的话就很麻烦
========== ======= ======= ====== ====== ===== ==== === == =
尘曲 七堇年
凡心所向,素履所往; 生如逆旅,一苇以航。
稣合于言,安之若素; 自言自语,无喜无悲。
凡心所向,素履所往; 生如逆旅,一苇以航。
三月桃花,四月欢唱; 两人一马,明日故乡。
流浪陌路,暖然绯凉; 写意人生,相识一场。
不关此世,不负己心; 我自倾杯,且君随意。