D. Lucky Permutation

D. Lucky Permutation

You are given a permutation p of length n.

In one operation, you can choose two indices 1i<jn and swap pi with pj.

Find the minimum number of operations needed to have exactly one inversion in the permutation.

A permutation is an array consisting of n distinct integers from 1 to n in arbitrary order. For example, [2,3,1,5,4] is a permutation, but [1,2,2] is not a permutation (2 appears twice in the array), and [1,3,4] is also not a permutation (n=3 but there is 4 in the array).

The number of inversions of a permutation p is the number of pairs of indices (i,j) such that 1i<jn and pi>pj.

Input

The first line contains a single integer t (1t104) — the number of test cases. The description of test cases follows.

The first line of each test case contains a single integer n (2n2105).

The second line of each test case contains n integers p1,p2,,pn (1pin). It is guaranteed that p is a permutation.

It is guaranteed that the sum of n over all test cases does not exceed 2105.

Output

For each test case output a single integer — the minimum number of operations needed to have exactly one inversion in the permutation. It can be proven that an answer always exists.

Example

input

复制代码
4
2
2 1
2
1 2
4
3 4 1 2
4
2 4 3 1
复制代码

output

0
1
3
1

Note

In the first test case, the permutation already satisfies the condition.

In the second test case, you can perform the operation with (i,j)=(1,2), after that the permutation will be [2,1] which has exactly one inversion.

In the third test case, it is not possible to satisfy the condition with less than 3 operations. However, if we perform 3 operations with (i,j) being (1,3),(2,4), and (3,4) in that order, the final permutation will be [1,2,4,3] which has exactly one inversion.

In the fourth test case, you can perform the operation with (i,j)=(2,4), after that the permutation will be [2,1,3,4] which has exactly one inversion.

 

解题思路

  首先由于最终的排列必须恰好存在一个逆序对,因此最终的排列就有下面这n1种可能。

  • 2,1,3,4,,n1,n
  • 1,3,2,4,,n1,n
  • 1,2,4,3,,n1,n

  ...

  • 1,2,3,4,,n,n1

  现在就是通过交换序列中的两个元素,使得通过最小的交换次数将原序列变成这n1个序列中的其中一个。

  由于是交换任意两个元素且求最小交换次数,因此容易联想到置换群。

  这里简单说一下置换群,对于原序列p,对每一个下标i连一条有向边ipi,那么就会建立一个由若干个环构成的图,这就叫做置换群。如果交换的两个元素在同一个环中,那么这个环就会裂开成两个环。如果交换的两个元素不在同一个环中,那么这两个环就和合并成一个环。可以通过并查集来求得原序列有多少个环(也就是求有多少个连通块)。

  很明显,一个长度为n的升序排列就构成了n个环,假设一开始原序列有cnt个环,由于要将原序列变成升序的排列,因此需要将cnt个环裂成n个环,由于每次交换最多可以将一个环裂开成两个,因此至少需要交换ncnt次。

  因此一种方案是先将原序列变成升序的排列,然后再交换任意两个相邻的元素一次,就得到满足题目要求的排列,交换次数是ncnt+1,但这不一定是最优解。

  其实可以发现最终的序列有n1个环,并且其中一个环的大小为2,环中的两个元素相差恰好为1。因此如果原序列的某个环本来就存在两个差值为1的元素,那么就直接把这两个元素保留在环中,那么最后最小的交换次数就是ncnt1。而如果原序列的每个环都不存在两个差值为1的元素,那么在变成升序序列后还要交换两个相邻元素,答案就是ncnt+1

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10;
 7 
 8 int a[N];
 9 int fa[N];
10 
11 int find(int x) {
12     return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
13 }
14 
15 void solve() {
16     int n;
17     scanf("%d", &n);
18     for (int i = 1; i <= n; i++) {
19         scanf("%d", a + i);
20         fa[i] = i;
21     }
22     int cnt = n;    // 一开始有n个环
23     for (int i = 1; i <= n; i++) {
24         int x = find(i), y = find(a[i]);    // i -> a[i]
25         if (x != y) {
26             fa[x] = y;
27             cnt--;  // 合并,环少了一个
28         }
29     }
30     for (int i = 1; i < n; i++) {
31         if (find(i) == find(i + 1)) {   // 两个相邻的元素在同一个环中
32             printf("%d\n", n - cnt - 1);
33             return;
34         }
35     }
36     printf("%d\n", n - cnt + 1);
37 }
38 
39 int main() {
40     int t;
41     scanf("%d", &t);
42     while (t--) {
43         solve();
44     }
45     
46     return 0;
47 }
复制代码

 

参考资料

  Codeforces Round #842 (Div. 2) Editorial:https://codeforces.com/blog/entry/110901

  Codeforces Round #842 (Div. 2) D(置换环):https://zhuanlan.zhihu.com/p/596949353

posted @   onlyblues  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-02-09 拖拉机
Web Analytics
点击右上角即可分享
微信分享提示