[USACO07FEB] Cow Sorting G
Desprition
农夫 JOHN 准备把他的 \(N\) 头牛排队以便于行动。因为脾气大的牛有可能会捣乱,JOHN 想把牛按脾气的大小排序。在排序过程中,JOHN 可以交换任意两头牛的位置。因为脾气大的牛不好移动,JOHN 需要 \(X+Y\) 秒来交换脾气值为 \(X\) 和 \(Y\) 的两头牛。 请帮 JOHN 计算把所有牛排好序的最短时间。
Solution
首先,因为起始顺序和最后的位置顺序都是固定的,所以置换是确定的 \(\begin{pmatrix}a_i&a_{i+1}&a_{i+2}&……&a_n\\b_i&b_{i+1}&b_{i+2}&……&b_n\end{pmatrix}\);
找出所有轮换(循环节)。
对于每个轮换来说,其所包含的牛肯定是在轮换内部交换,每头牛至少交换一次。但是最小花费却并不能因此确定……因为无法保证轮换内的最小花费就是整个序列的最小花费。
有两种情况:
- 仅限轮换内部交换;
贪心可知每次交换都应该是脾气之最小的牛和另外一头牛交换。一遍一遍换,知道排好顺序。
- 有轮换外的牛参与交换。
其实就是将轮换外脾气值最小的牛 \(x\) 换进来,每次参与交换,轮换内的牛排好位置之后再把 \(x\) 换出去。
Code
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 5, inf = 1e5;
int n,a[N],b[N],id[N],bel[N],tot,mi,sz;
ll ans;
vector<int> G[N];
int main() {
scanf("%d",&n);
for(int i = 1; i <= n; i ++) scanf("%d",&a[i]), b[i] = a[i];
sort(b + 1,b + 1 + n);
for(int i = 1; i <= n; i ++) id[b[i]] = i;
int node,nex;
for(int i = 1; i <= n; i ++) {//求轮换
node = id[a[i]];
if(bel[node]) continue;
nex = id[a[node]], bel[node] = ++tot;
while(nex != node) {
bel[nex] = tot;
nex = id[a[nex]];
}
}
for(int i = 1; i <= n; i ++) G[bel[id[a[i]]]].push_back(a[i]);
for(int i = 1; i <= tot; i ++) {
mi = inf, sz = G[i].size();
for(int j = 0; j < sz; j ++) {
ans += G[i][j], mi = min(mi,G[i][j]);//每头牛都至少交换一次。
}
ans -= mi, ans += min((sz - 1) * mi,b[1] * (sz + 1) + mi * 2); //上述两种情况取最小值。
}
printf("%lld",ans);
return 0;
}