题目大意:给定一个序列,每次可以选择两个元素a和b交换,代价为a+b,求将其变为升序的最小代价。
解题思路:将序列表示为循环相乘的形式,假设a的目标位置为x,x下的数为b,那么a和b必定在同一个循环内。容易想到将各个循环分别求解,用循环内最小的数作为媒介。但注意到两个循环A和B,将它们乘上对换(a,b),其中a属于A,b属于B,A和B将合成一个大循环C,而C内的最小的数比A或者B内的最小的数要小,将C整体求解加上合并代价a+b可能比A和B分别求解要优。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
using namespace std;
vector<int> tb[10012];
int n, a[10010], b[10010], g[10010], f[100010], M[10010], tot[10010];
bool vis[10010];
int main()
{
while (scanf("%d", &n) != EOF)
{
int i;
for (i = 1; i <= 10010; i++) tb[i].clear();
for (i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b + 1, b + n + 1);
for (i = 1; i <= n; i++)
{
f[b[i]] = i;
g[i] = b[i];
}
int cnt = 0;
memset(M, 0x3f, sizeof(M));
memset(vis, false, sizeof(vis));
memset(tot, 0, sizeof(tot));
for (i = 1; i <= n; i++)
{
if (vis[i]) continue;
int j = i;
cnt++;
while (!vis[j])
{
vis[j] = true;
tb[cnt].push_back(g[j]);
tot[cnt] += g[j];
if (g[j] < M[cnt]) M[cnt] = g[j];
j = f[a[j]];
}
}
/*for (i = 1; i <= cnt; i++)
{
for (int j = 0; j < tb[i].size(); j++) cout << tb[i][j] << " ";
cout << endl;
}*/
int ans = 0;
for (i = 1; i <= cnt; i++)
{
if (tb[i].size() == 1) continue;
int cost1 = M[i] * (tb[i].size() - 2) + tot[i];
int cost2 = M[i] + b[1] + b[1] * tb[i].size() + tot[i];
//cout << cost1 << " " << cost2 << endl;
ans += min(cost1, cost2);
}
cout << ans << endl;
}
}