一、题目链接
二、题目分析
(一)算法标签
图论 环 置换群 贪心
(二)解题思路
解法一思路:
这里以第一个样例为例,要满足题目要求,即最后的排列应该是1, 2, 3, 4, 5
则3要跟2换位置,2要跟1换位置,1要跟3换位置 (即1
→
\rightarrow
→ 3
→
\rightarrow
→ 2
→
\rightarrow
→ 1),从而形成一个环;
5要跟4换位置,4要跟5换位置,(即4
→
\rightarrow
→ 5
→
\rightarrow
→ 4),又形成一个环。
我们知道,
对于一个环,交换环中两个元素的位置,则必然会裂开成2个环(环的数量加1)
这里以环(1
→
\rightarrow
→ 3
→
\rightarrow
→ 2
→
\rightarrow
→ 1)为例,如果交换1和3,则环裂开成环(1
→
\rightarrow
→ 1) 和 环(3
→
\rightarrow
→ 2
→
\rightarrow
→ 3) [即原来指向1的现在指向3,原来指向3的现在指向1]
对于两个不同的环,交换这两个不同环中的元素,则这两个环会合并成一个环
这里以环(1
→
\rightarrow
→ 3
→
\rightarrow
→ 2
→
\rightarrow
→ 1) 和 环(4
→
\rightarrow
→ 5
→
\rightarrow
→ 4)为例,交换1和4,则这两个环会合并成环(1
→
\rightarrow
→ 3
→
\rightarrow
→ 2
→
\rightarrow
→ 4
→
\rightarrow
→ 5
→
\rightarrow
→ 1)[即原来指向1的现在指向4,原来指向4的现在指向1]
要满足题目要求(即要裂开成n个自环,即1要到1的位置,2要到2的位置,以此类推),也就是说从cnt
个环要变成n个自环,则至少要交换n - cnt
次(其中cnt
为环的个数)
那么,如何求环的个数呢?
对于每一个数,如果没有在环中,则将这个数和这个数的应该要去的数的位置也标记成true,直到某个为true,则说明它属于另一个环,再对下一个数进行重复操作,代码如下:
for (int i = 1; i <= n; i ++ )
{
if (!st[i])
{
cnt ++ ; // 环的数量+1
for (int j = i; !st[j]; j = b[j]) // 下标为j的元素应该要到元素b[j]的位置
{
st[j] = true;
}
}
}
解法二思路:
遍历每一个数,如果当前数不在应在的位置上,则交换当前数和它应该在的位置上的数,直到当前数在应该在的位置上为止。
每交换一次,则答案数+1
三、AC代码
解法一:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
int b[N];
bool st[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
int cnt = 0;
for (int i = 1; i <= n; i ++ )
{
if (!st[i])
{
cnt ++ ; // 环的数量+1
for (int j = i; !st[j]; j = b[j]) // 下标为j的元素应该要到元素b[j]的位置
{
st[j] = true;
}
}
}
cout << n - cnt << endl;
return 0;
}
解法二:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4 + 10;
int b[N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]);
int cnt = 0;
for (int i = 1; i <= n; i ++ )
{
while (b[i] != i)
{
swap(b[i], b[b[i]]);
cnt ++ ;
}
}
cout << cnt << endl;
return 0;
}