题目大意:给出一个长度为n的排列a,每次操作可以交换两个数,问最少几次操作能使排列中有且仅有一个逆序对
2<=n<=2e5;
思路:有且只有一个逆序对的情况就是将这个排列从小到大排序,然后在交换两个相邻数,所以我们先考虑把排列排序,就需要用到置换环,对于任意的i属于1到n从i到ai建一条有向边,例如2,3,4,1,5这个排列的置换环如下:
如果我们交换2,1使1被放到排序好的位置,就相当于将1的出边指向自己,然后4的出边指向2,类似于链表删除元素的操作,所以对于有四个元素的环,3次操作就能使所有数排好序,所以要使整个列排好序只需要n-环的数量次操作,然后要有一个逆序对只需再额外交换两个相邻数即可,但是在排序的过程可能出现过两个相邻元素成一个点数为2的环,这样的环不需要拆,操作数不需要+1,而需要-1,所以我们对于在dfs时,对于每个环都用map记录当前访问过的元素,如果遇到过+1或-1的元素,就操作数-1
//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int e[N];
bool vis[N];
bool flag;
map<int, int>ma;
void dfs(int u)
{//遍历环
if (vis[u])
return;
vis[u] = 1;
ma[u] = 1;//记录在环中出现过的点的编号
if (ma[u - 1] || ma[u + 1])
flag = 1;//出现过相邻点
dfs(e[u]);
}
int main()
{
cin.tie(0);
ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
vis[i] = 0;//用于dfs避免重复遍历以及判环
}
flag = 0;..判断是+1还是-1
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
e[i] = x;//单向边
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (vis[i])//这里一定要有,否则时间复杂度是O(n方)
continue;
dfs(i);
ma.clear();
ans++;//统计环的数量
}
if (!flag)
{
cout << n - ans + 1 << endl;
}
else
{
cout << n - ans - 1 << endl;
}
}
return 0;
}