置换环
作用:求解数组排序元素间所需最小交换次数这类问题。
思想:置换环将每个元素指向其排序后应在的位置,最终首位相连形成一个环(若数字在最终位置,则其自身成环),可知元素之间的交换只会在同一个环内进行,而每个环内的最小交换次数为\(环上元素个数-1\)。
则总交换次数:\(ans = \sum_{i = 1}^n(cyclesize_i-1)=数组长度-环的个数\)
题意:给定一个长度为$ n(n≤2×10^5)$ 的排列 \(p\)。你可以多次交换排列中的任意两个数。问,最少多少次交换,可以使得排列中,有且仅有一个逆序对。
思路:利用上面的结论,我们要换的次数是\(n-环数+1\)。但是如果有相邻的两个数的话那么可以通过减少一次交换,使得其贡献出一个逆序对。次数的交换次数为\(n-环数-1\)
code:
int n, p[N], vis[N];
map<int, bool>ring;//记录环
bool flag = 0;
void dfs(int u) {
if (vis[u]) return;
vis[u] = 1;
ring[u] = 1;
int v = p[u];// u -> v
if (ring[u - 1] || ring[u + 1])flag = 1;//记录是否有相邻的两个点
dfs(v);
}
void slove() {
cin >> n;
for (int i = 1; i <= n; i++)vis[i] = 0;
for (int i = 1; i <= n; i++)cin >> p[i];
int cur = 0;
flag = 0;
for (int i = 1; i <= n; i++) {
if (!vis[i]) {
cur++;
ring.clear();
dfs(i);
}
}
if (flag)cout << n - cur - 1 << endl;
else cout << n - cur + 1 << endl;
}
题意:给你一个排列(\(1~n\)),这个排列被认为是简单的需要满足下面其中一个条件:
对于每一个\(i(1\le i\le n)\)
- \(p_i = i\)
- \(p_{p_i} = i\)
问你最少交换多少次使得这个排列是简单的。
思路:考虑置换环。
对于\(i\rightarrow p[i] \rightarrow p[p[i]] \rightarrow p[p[p[i]]] \rightarrow ... \rightarrow i\) 这样子的会构成一个环。需要满足条件的话,环的长度需要\(\ge 2\)。即,最后这个数列转化为图一定是若干个自环和若干个两数环所组成的图。
对于环长度\(\le 2\)的,要进行交换操作让环长度变短。我们发现让\(p[p[i]] = i\)比让\(p[i] = i\)更优,为什么呢?
我们设\(mp[i]\)为数值为\(i\)的下标。以\(2,3,4,1\)为例:
\(mp[1] = 4\),\(p[1] = 2\),我们想让\(p[p[1]] = 1\)即\(p[2] = 1\),那么\(p[mp[1]] = p[2]\),即\(p[4] = 3\)
交换完一次之后变成了\(2,1,4,3\)。同时更新\(mp[1] = 2,mp[3] = 4\)
这样必定可以一次修改两个数,是优于\(p[i]\)改成\(i\)的,那么对于环长度为\(size\)的,交换次数为\(\lfloor\dfrac{size-1}{2} \rfloor\)。
#include <bits/stdc++.h>
using namespace std;
struct DSU {
std::vector<int> f, siz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n + 1);
std::iota(f.begin(), f.end(), 0);
siz.assign(n + 1, 1);
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
siz[x] += siz[y];
f[y] = x;
return true;
}
int size(int x) {
return siz[find(x)];
}
};
int main()
{
int t; cin>>t;
while(t--)
{
int n; cin>>n;
DSU d(n);
for(int i = 1;i <= n; i++)
{
int x; cin>>x;
d.merge(i,x);
}
set<int>s;
for(int i = 1;i <= n; i++){
d.f[i] = d.find(i);
s.insert(d.f[i]);
}
int ans = 0;
for(auto x : s)
{
ans += (d.siz[x]-1)/2;
}
cout<<ans<<"\n";
}
return 0;
}
或者直接模拟上述过程也是可以的:
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int t; cin>>t;
while(t--)
{
int n; cin>>n;
map<int,int>mp;
vector<int>p(n+1);
for(int i = 1;i <= n; i++){
cin>>p[i];
mp[p[i]] = i;
}
int ans = 0;
for(int i = 1;i <= n; i++)
{
if(p[i] == i || p[p[i]] == i)
continue;
int s = mp[i],t = p[i];
swap(p[s],p[t]);
mp[p[s]] = s;
mp[p[t]] = t;
ans++;
}
cout<<ans<<"\n";
}
return 0;
}
//2 3 4 1
//2 1 4 3
//2 3 4 5 1
//2 1 4 5 3
//2 1 4 3 5