PAT sort with swap(0, *) (索引排序)
稍微有点难度的一道题,开始写一个给超时了,直接把for循环套在了while里,硬生生的把复杂度从O(mN)拉到了O(N^2)
为了解决这道题,首先需要知道一些知识:
N个数字的排列由若干个独立的环构成。
这句话什么意思?
比如对于一个从
(
0
,
N
−
1
)
(0,N-1)
(0,N−1)的排列,表示成数组的形式,记为
A
[
N
]
A[N]
A[N],令
j
=
A
[
i
]
j=A[i]
j=A[i],那么沿着
A
[
j
]
A[j]
A[j]这个路径下去,最后会回到起点。
总体来说就是如果
A
[
i
]
=
s
A[i]=s
A[i]=s,
A
[
A
[
.
.
.
.
A
[
A
[
s
]
]
.
.
.
.
]
]
=
s
A[A[....A[A[s]]....]] = s
A[A[....A[A[s]]....]]=s
根据这个题的意思,我们每次交换0和任意一个元素,肯定想到的就是
和与0这个位置对应的元素所在的位置交换,比如0的位置在7,7的位置在1,我们就要交换这两个位置,那么7就被放到了正确的位置,0就在位置1,重复进行。
运气好的话一趟就能把整个序列的元素有序,那么交换的次数就是
N
−
1
N-1
N−1这么多次,为什么呢,因为每次可以使得一个元素有序,但是最后一次是使得剩下的最后两个元素同时有序,所以是
N
−
1
N-1
N−1次。
如果运气不好,还没有交换完的时候,0就回到了他的正确位置,也就是说
A
[
0
]
=
0
A[0]=0
A[0]=0了,那么我就可以考虑对于还没有交换完的剩下的环,我们把0加进去,然后让0随便同其中一个元素交换位置(同哪一个元素交换不重要),然后继续沿着这个环重复上述操作,知道0又到达了他的正确位置。这种情况下要交换多少次呢,首先假设这个环的元素是
N
i
N_i
Ni,加上0之后为
N
i
+
1
N_i+1
Ni+1,再根据上面所说的可得到交换次数是
N
i
+
1
−
1
+
1
=
N
i
+
1
N_i+1-1+1=N_i+1
Ni+1−1+1=Ni+1,最后的加一是什么呢,就是把0同任意元素交换的一次也加上。
当然写的时候我们只需要统计每个环的个数,包含0的环交换次数就是环内元素个数
N
N
N减去1,不包含0的环就是
N
+
1
N+1
N+1。
由于环是相互独立的,只有一个环是包含0的。
#include <iostream>
using namespace std;
const int max_n = 1e5;
int N;
int pos[max_n + 10];
int main() {
cin >> N;
for (int i = 0; i < N; i++) {
// 其实并不需要用的输入的数列,只需要知道每个元素的位置就行了,所以只需要一个位置数组。pos[i]代表第i个元素的位置。
int num; scanf("%d", &num);
pos[num] = i;
}
int res = 0;
for (int i = 0; i < N; i++) {
int now = pos[i];
int Elements = 1;
if (now == pos[now]) continue;
//沿着环统计环内元素的个数,也就是索引排序的写法。
while (now != pos[now]) {
int nxt = pos[now];
pos[now] = now;
pos[i] = nxt;
now = pos[i];
Elements++;
}
//只有一个环是包含0的,就是从0开始的这个。
if(i == 0) res += Elements - 1;
else res += Elements + 1;
}
cout << res << endl;
return 0;
}