题意:
给定一个长度为 n n n 的排列 p p p,对于 1 ≤ i < j ≤ n 1\leq i
数据范围: 1 ≤ n ≤ 1 0 5 1\leq n\leq 10^5 1≤n≤105
题解:
首先考虑将 p p p 转换为逆序对为 0 0 0 的排列,即 [ 1 , 2 , ⋯ , n − 1 , n ] [1,2,\cdots,n-1,n] [1,2,⋯,n−1,n] 。
对于排列 p p p ,可以构成 c c c 个环, i i i 所在的环为: [ i , p i , p p i , p p p i , . . . ] [i,p_i,p_{p_i},p_{p_{p_i}},...] [i,pi,ppi,pppi,...] 。如此,将 p p p 转换为 [ 1 , 2 , ⋯ , n − 1 , n ] [1,2,\cdots,n-1,n] [1,2,⋯,n−1,n] 需要的最少次数是多少呢?
故对于每个长度为 l e n i len_i leni 的环,交换次数为 l e n i − 1 len_i-1 leni−1,共有 c c c 个环,故总交换次数为: ∑ i = 1 c ( l e n i − 1 ) = ( ∑ i = 1 c l e n i ) − c = n − c \sum\limits_{i=1}^c (len_i-1)=(\sum\limits_{i=1}^clen_i)-c=n-c i=1∑c(leni−1)=(i=1∑cleni)−c=n−c。
接下来我们考虑将 p p p 转换为逆序对数量为 1 1 1 的排列。
上面已经处理得到了将 p p p 转换为 [ 1 , 2 , ⋯ , n − 1 , n ] [1,2,\cdots,n-1,n] [1,2,⋯,n−1,n] 的环的数量,以及每个环中的数。
一共有 n − 1 n-1 n−1 种逆序对数量为 1 1 1 的排列,即:
考虑将排列 p p p 转换为排列 ① 。
对于其余的逆序对数量为 1 1 1 的排列是一样的。
证明:
举个 1 1 1:
1 2 3 4 5 6 7 8
2 3 4 1 5 6 7 8
1->2->3->4->1
对于 环 1->2->3->4->1
,交换 1 1 1 和 2 2 2
交换如下:
2 1 3 4 5 6 7 8
2 3 4 1 5 6 7 8
2->2
1->3->4->1
交换后一个环 1->2->3->4->1
变成两个环,环 2->2
和环 1->3->4->1
。
举个 2 2 2:
1 2 3 4
1 2 3 4
1->1
2->2
3->3
4->4
对于 环 1->1
和 2->2
,交换 1 1 1 和 2 2 2
交换如下:
1 2 3 4
2 1 3 4
1->2->1
3->3
4->4
交换后两个环 1->1
和 2->2
变成一个环 1->2->1
。
故可以得出
来考虑一个单纯的数交换问题:
1 2 3 4
2 3 4 1
1. 1->2->3->4->1
如果交换 1 1 1 和 3 3 3
交换后为:
1 2 3 4
2 1 4 3
1->2->1
3->4->3
可以发现,交换的点 1 1 1 和点 3 3 3 的指向未变,而指向点 1 1 1 的 4 4 4 和指向点 3 3 3 的 2 2 2 的方向分别交换,即 2->3
变成了 2->1
,4->1
变成了 4->3
。
再来考虑一个例子:
1 2 3 4
1 3 4 2
1->1
2->3->4->2
如果交换 1 1 1 和 2 2 2
交换后为:
1 2 3 4
2 3 4 1
1->2->3->4->1
可以发现,交换前后, 2 2 2 的指向未变, 1 1 1 的指向好像变成了 2 2 2 ,但要清楚,1->1
这个环可以看出一个 1(1)->1(2)
和 1(2)->1(1)
两条边。一方面 1 1 1 作为入度点,一方面 1 1 1 作为出度点。
可以看到环的最后, 1 1 1 仍然指向 1 1 1 ,但是 1 1 1 也指向 2 2 2。故可以像上述将 1 1 1 拆点后来看,1(1)->2,4->1(2),1(2)->1(1)
。
再来思考:
1(2)
。 2 2 2 的指向不变。1(1)
的 1(2)
,交换后指向了 2 2 2,1(2)
的指向不变,仍然指向 1(1)
。故两个环变成了一个环。
故得证:交换的 i i i 和 j j j 在 p p p 中若在同一环上,交换后裂成两个环,交换的 i i i 和 j j j 在 p p p 中若在两个环上,交换后合并为一个环。
代码:
#include
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1), group(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
int circle = 0;
for (int i = 1; i <= n; ++i) {
if (group[i] != 0) continue;
circle += 1;
int j = i;
while (!group[j]) {
group[j] = i;
j = a[j];
}
}
int ans = n + 1;
for (int i = 1; i < n; ++i) {
if (group[i] == group[i + 1]) {
ans = min(ans, n - (circle + 1));
} else {
ans = min(ans, n - (circle - 1));
}
}
cout << ans << "\n";
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}