#include <iostream>
#define MAXN 100000
using namespace std;
int main() {
int pos[MAXN];
bool used[MAXN];
int N, num, sort = 0, connected_set = 0; // sort:已排好序的数字 connected_set:“连通集”数量
cin >> N;
for( int i = 0; i < N; i++ ) {
cin >> num;
pos[num] = i;
used[num] = false;
if( i > 0 && pos[num] == num ) { // 除了0, 因为0要用来和其他数交换, 就算在位置0也不能被视为排好序
used[num] = true;
sort++;
}
}
for( int i = 0; i < N; i++ )
if( used[i] == false ) {
for( num = i; pos[num] != i; num = pos[num], used[num] = true );
used[num] = true;
connected_set++;
}
cout << (N-sort) - 1 + (connected_set-1);
}
一开始没多想直接模拟这种交换排序,果然就超时了
先讲讲交换排序的思路(注意必须是0和其他数交换)
类似贪心的思想,比如0在位置7,就让0和数字7交换,从而7就在正确位置了,以此类推
直到0在位置0时,此时有可能还有其他数字没有排好序
所以只能让0和一个没排好序的数先交换,比如位置1上是数字5,0与5交换
产生一次额外交换,这就是多个连通集的影响(后面讲)
此后再重复最初的步骤,直到0重新到位置0,再检查有没有还没排好序的数字...
于是开始试图找旁门左道
1.首先统计已经在正确位置的数字个数sort,无视这些数字
总数N - sort
2.计算“连通集”数
第一行为原序列,第二行为从0~N的位置标号
数字 3 5 7 2 6 4 9 0 8 1
位置 0 1 2 3 4 5 6 7 8 9
位置0对应数字3,然后位置3对应数字2,再位置2对应数字7,又位置7对应数字0
形成一个“闭环”,称之为一个“连通集”
剩下位置1对应数字5,位置5对应数字4,位置4对应数字6,位置6对应数字9,位置9对应数字1
是另一个“连通集”,然后没有剩下的数字了
一个连通集即整个序列形成闭环,不会对交换次数有影响
此后每多一个连通集,都要额外让0交换一次
(总数N - sort) + (connected_set -1)
3.计算
最后还要再减一次1
可以这样理解,把N个都不在正确位置的数字逐个放进正确位置需要N步操作
交换也是达到类似这种效果,每次交换让一个数字到正确位置
但是注意最后一次交换可以让两个数字到正确位置,即被交换的数字和0本身!
所以只需要N-1次操作
综上, 交换次数 = (总数N - sort) + (connected_set -1) - 1