code
本题的数据范围是1e4,我们用了两层循环,刚好1e8,倘若本题给的数据范围是1e5的话这道题暴力我们就过不了了。
置换群法
首先我们可以发现,这道题并不是要求我们两两交换,我们只需要把a[i]放到其该放的位置即可。
我们将其下标标记出来看看:
我们可以将其抽象为环:
我们发现规律:
一. 一个环有n条边n个点
二. 一条边只有一个出度 (只指向一个点1),且一个点只有1个进度(只被一条边指向)
现在我们让1这个值指向下标为1的位置:
用环表示就是:
因此我们发现每次交换两个点就会分裂出来一个环。
那么我们最终期望的环形图就是所有的点都自成环:
第二种情况就是不同环的进行交换:
我们发现两个不同的环进行交换必然会进行融环:
总结:对于交换同一个环中的节点,就是裂成两个环。
刚开始有k个环,我们交换同一个环中的节点使其变为一个有序数列,那么就会裂成多个环。
最后,总共会有n个环。最后我们要把整体合并变为一个有序数组,那么就需要合环(消环)。
分裂了多少环就需要消多少环。一共分裂了n-k个环。
实际上也是交换了n-k次。
code
我们可以用并查集去做。
首先读入题目给的顺序到一个新数组a中:
3 1 2 5 4
然后我们的并查集数组存正确的顺序:
1 2 3 4 5
这个时候我们去判断是否在一个环:
我们判断两个点是否在同一个环的方法是判断它们的根是否相同,如果两个点是同一个根,说明它们就在同一个环中,否则就不处于同一个环,那么我们就需要合并环。
我们合并环的方法是将其正确下标赋值给它。
我们判断两个点是否是同一个根的方法就是让它们的根为自身,这样可以直接判断两个数是否相等来确定两个点是否是同一个根。
# include<iostream>
# include<algorithm>
using namespace std;
const int N = 1e4+10;
int n, res;
int a[N], f[N], cnt[N];
int find(int x)
{
cout<<"x: "<<x<<endl;
cout<<"f[x]: "<<f[x]<<endl;
if(x!=f[x]) return f[x] = find(f[x]);
return f[x];
}
int main()
{
cin>>n;
for(int i=1; i<=n; ++i) f[i] = i; //初始化并查集中的数组f[]
for(int i=1; i<=n; ++i) cin>>a[i]; //读入数据
for(int i=1; i<=n; ++i)
{
int fa = find(a[i]), fb = find(i);
if(fa!=fb) f[fa] = fb; //如果不在一个集合,就合并集合
cout<<"fa: "<<fa<<endl;
cout<<"fb: "<<fb<<endl;
cout<<"f[fa]: "<<f[fa]<<endl;
}
for(int i=1; i<=n; ++i) //判断回环的个数(并查集的大小)
{
int fa = find(a[i]);
cout<<" a[i]: "<<a[i]<<endl;
cout<<" fa: "<<fa<<endl;
if(!cnt[fa]) res++;
cnt[fa]++;
}
cout<<n-res<<endl;
return 0;
}