Description

牛客幼儿园的小朋友课间操时间需要按照学号从小到大排队,但是他们太小了只能站成一列顺序却不对,现在幼儿园的阿姨需要帮忙交换小朋友的位置让他们最终有序,阿姨希望能尽快完成交换操作,问最少需要交换多少次,才能使得小朋友们从小到大排好。
注意:每个小朋友的学号不同,但是未必连续,因为可能有小朋友请假了没有来。

Solution

这道题有点意思, 一开始以为是求逆序数, 但是仔细一看他跟我们之前求的最小交换数的条件有所不同
简单的说,这道题不限定交换的是相邻两个,因此我们可以自由交换
我们可以推断出有一个循环节,就可以少交换一次,因为n个元素的循环节,只需交换n-1次即可有序。
那么对于整个序列来说,最少交换次数为 元素总数-循环节个数。
那么就是个结论题,结论是 数字个数 - 循环节的个数
什么是循环节呢?我们知道每一个数字都有他们应该所在的位置和当前的位置
比如 a 应该放在 b, b 要放在 c..... 这样一直循环下去直到有一个数字的位置放在我们找过的位置中
即会形成一个环, 显然我们可以当成图论题来做了,对于每个点, 如果所在位置不是它应该处在的位置
那么我们对当前位置和应处位置建边,最后会形成一个图
我们在图上dfs找有多少个环即可

Code

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e6 + 5;

int a[N], b[N], cnt;
bool vis[N];
vector<int> G[N];
void addedge(int u, int v) {
  G[u].push_back(v), G[v].push_back(u);
}
void dfs(int x) {
  for(int i = 0; i < G[x].size(); i++) {
    int v = G[x][i];
    if(!vis[v]) {
      vis[v] = 1;
      dfs(v);
    }
  }
}
int main() {
  ios::sync_with_stdio(false), cin.tie(nullptr);
  int n;
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> a[i];
    b[i] = a[i];
  }
  sort(b + 1, b + 1 + n);
  map<int, int> mp;
  for(int i = 1; i <= n; i++) {
    mp[b[i]] = i; // 应该在的位置
  }
  for(int i = 1; i <= n; i++) {
    if(a[i] != b[i]) {
      addedge(i, mp[a[i]]);
      addedge(mp[a[i]], i);
    }
  }
  int ans = 0;
  for(int i = 1; i <= n; i++) {
    if(!vis[i]) {
      vis[i] = 1;
      ans++;
      dfs(i);
    }
  }
  cout << n - ans << "\n";
  return 0;
}