题意:给你一个无序数列,让你两两交换将其排成一个非递减的序列,每次交换的花费交换的两个数之和,问你最小的花费
思路:首先了解一下什么是置换,置换即定义S = {1,...,n}到其自身的一个双射函数f。那么我们将其写为若干个不相交的循环的乘积形式(A1, A2, ... Ap1)(B1, B2, ... Bp2)... ...例如
数组 9 4 5 7 3 1
下标 1 2 3 4 5 6
排序后下标 6 3 4 5 2 1
让我们看下标 1->6->1 一个循环 (9 1)
2->3->4->5->2 一个循环(4 5 7 3)、
循环节内的每一个元素都在不合适的位置上,因此长度为l循环节内部至少需要进行(l - 1)次互换可使其有序。
我们可以用一个贪心的思想,每次用循环最小的那个数进行两两交换,定义循环的所有数总和为sum,循环的最小值为k,循环长度为len,那么一个循环的花费为 sum-k+(len-1)*k => sum+(len-2)*k
但是其实还有另一种可能,用整个置换最小的那个和这个循环最小的进行换位,定义循环的所有数总和为sum,循环的最小值为k,循环长度为len,整个置换的最小值为kmin ,,那么一个循环的花费为 sum+(len+1)*kmin+k
每一次循环的最小值为 ans=ans+min(sum+(len-2)*k,sum+(len+1)*kmin+k)
组合数学置换资料https://wenku.baidu.com/view/9b8d9d32e87101f69e3195f8.html
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #define maxn 100005 using namespace std; struct node { int num; int id; }; bool cmp(node a,node b) { if(a.num<b.num) return true; else return false; } int main() { node data[maxn]; int visit[maxn]; int n; int minn=1<<25; int ans=0; while(scanf("%d",&n)!=-1) { for(int i=0;i<n;i++) { scanf("%d",&data[i].num); data[i].id=i; minn=min(minn,data[i].num); } memset(visit,0,sizeof(visit)); sort(data,data+n,cmp); for(int i=0;i<n;i++) { if(!visit[i]) { visit[i]=1; int tmp=data[i].id; int kmin=data[i].num; int sum=data[i].num; int len=1; while(tmp!=i) { visit[tmp]=1; len++; sum+=data[tmp].num; kmin=min(kmin,data[tmp].num); tmp=data[tmp].id; } ans+=min(sum+kmin*(len-2),sum+(len+1)*minn+kmin); } } printf("%d ",ans); } return 0; }