最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
题目开始给定一个下标从 $1$ 开始,长度为 $n$ 的数组,即 $\{1,2,\cdots,n\}$
再给定每个下标每轮移动到的新的下标的数组 $p[n]$
上面的话翻译一下就是:下标为 $i$ 的元素进行一轮移动会到 $p[i]$ 的下标上
问每个数字经过多少次变换,会回到原来的位置上。
解析
这类数字坐标移动是个比较经典的图论模型
我拿测试样例建个图,具体如下:
因为每个点的出度和入度都为 $1$,所以每组交换都会形成一个闭环
对应每个环中的节点个数 $t$,就是每个元素最终回到自己原来位置时需要的经过的交换轮数 $t$
那么本题就是求每个元素对应环中的节点个数
我们可以采用并查集维护各个连通块(环)的连通性,然后在并查集的头节点额外维护该并查集的节点个数即可
类似的图论模型还有经过多少次操作可以把每个连通块变成自环(容我找一下,等下上链接)
Code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int T, n;
int a[N], p[N], s[N];
int find(int x) // 并查集
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
scanf("%d", &T);
while (T -- )
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) p[i] = i, s[i] = 1;
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ )
{
if (a[i] != i)
{
int pa = find(i), pb = find(a[i]);
if (pa != pb)
{
s[pb] += s[pa];
p[pa] = pb;
}
}
}
for (int i = 1; i <= n; ++ i) cout << s[find(i)] << " ";
cout << endl;
}
return 0;
}
解释的语言好简练,好精华,好喜欢!!!
写得太好啦
向大佬看齐
s是怎么更新的 能具体分析一下原理和举出一个样例吗
## 并查集
### 操作:
1.
合并
两个集合2.
查询
某个元素的祖宗
结点### 拓展应用:
1. 记录每个
集合的大小
-> 绑定到根结点
上2. 记录每个
点到根结点的距离
-> 绑定到每个元素
上本题的操作是:记录每个
集合的大小
-> 绑定到根结点
上合并两个根节点的时候,把其中一个根节点维护的并查集元素数量
s[pa]
累加到另一个并查集的根节点中然后再把两个并查集合并
可是这步完成以后并不是真正的答案啊 我拿y总的程序输出出来了
什么意思啊?
s不是只有在if底下的一步
是的
你试把这部之后他的j 和相应的 pj sj输出
只有这一步更新 但是把这步完成之后的s输出却不是最后答案 但是最后的s数组却是最终答案
最后的答案是在并查集的根节点上维护的,而不是单一的节点(单一节点的
s
值是无效的)只有根节点的
s
值才是我们要的答案所以数组没有完全遍历结束之前,答案都还没有出来(因为要合并的集合可能还没有合并到一个并查集内)
这个操作是并查集的经典应用,我建议看一下提高课 “搭配购买” 这一道题
可能视频课会比纯文字讲的更清楚一点
但是这不是单一的一步更新吗只在if底下一次更新 能拿个简单的样例手动模拟一下吗
下午等我找个时间画一下吧,上午比较忙,抱歉啊
有了吗
并查集维护集合大小
如果还有不懂,建议百度:并查集如何维护集合大小
这不是一张图能够解释的了的
点赞
大佬为啥这两行不能交换
s[pb] += s[pa];
p[pa] = pb;
可以交换啊,你看看是不是其他地方写错了
y总写的那种不能交换,这个题解里的实现把a和b的父节点都存下来了,如果没有先存下来,先更新了a的父节点,然后s[find(a)]的时候就错了
对y总就是
很像蓝桥杯的交换瓶子这题
是的,没错
orz
orz