问题描述
有重量为wi(i = 0, 1, …, n - 1)的n个货物排成一列。现在要用机械臂将这些货物排序。机械臂每次操作可以提起货物i和货物j并交换二者位置,同时产生wi + wj的成本。机械臂的操作次数没有限制。
请求出将给定货物列按重量升序排列时所需总成本的最小值。
输入: 第1行输入整数n。第2行输入n个整数wi(i = 0, 1, 2, …, n - 1),用空格隔开。
输出: 在1行内输出最小值。
限制:
1 ≤ n ≤ 1000
0 ≤ wi ≤ 10000
wi的值互不重复
输入示例
5
1 5 3 4 2
输出示例
7
讲解
我们以W = {4, 3, 2, 7, 1, 6, 5}为例进行分析。现在的目标是求出将W重排为{1, 2, 3, 4, 5, 6, 7}时所需的最小成本。我们不妨先画出一张标出每个元素最终将移动到哪个位置的草图。(如图1所示)
图中有3个闭合的圆,分别是4 - 7 - 5 - 1 - 4;3 - 2 - 3;6 - 6。现在我们来分析每个圆所需的最小成本。
为自环即1长度的圆无需移动,成本为0。
长度为2交换位置即可,成本为二者之和。如3 - 2 - 3的成本为3 + 2 = 5。
对于长度大于等于3的圆。(如图2所示)在处理4 - 7 - 5 - 1 - 4时,通过1来移动其他元素可以保证成本最小。
设圆中的元素为wi,圆内元素数为n,那么此时的成本为
Σ w i + ( n − 2 ) ∗ m i n ( w i ) \Sigma wi+ (n-2)*min(wi) Σwi+(n−2)∗min(wi)
由于各元素至少移动一次,所以有 Σ w i \Sigma wi Σwi。再加上最小值在最后一次交换前要移动(n - 2)次,所以有(n - 2) * min(wi)。上式在n = 2时也成立。
于是最小成本为(5 + 0) + (17 + 2) = 24。
这个方法乍眼一看像那么回事,但是你仔细一寻思发现还存在反例。(如图3所示)
如果套用之前的方法,1 - 2 - 1成本为3,8 - 10 - 9 - 7 - 8成本为48,总成本为51。
但若先将7和1交换,把圆8 - 10 - 9 - 7 - 8改为8 - 10 - 9 - 1 - 8,这部分的成本就成了28 + 2 * 1 = 30。然后再加上两次交换7和1以及圆1 - 2 - 1的成本,总成本为49。可见,即便我们多加了两次1和7交换的成本,总成本依然小于之前的方法,显然有时从圆外借元素来移动,能让成本更低。
设外圆的元素为x。借元素增加的成本为 2 ∗ ( m i n ( w i ) + x ) 2*(min(wi)+x) 2∗(min(wi)+x),节约的成本为 ( n − 1 ) ∗ ( m i n ( w i ) − x ) (n-1)*(min(wi)-x) (n−1)∗(min(wi)−x)此时该部分的总成本为
Σ w i + ( n − 2 ) ∗ m i n ( w i ) + 2 ∗ ( m i n ( w i ) + x ) − ( n − 1 ) ∗ ( m i n ( w i ) − x ) = Σ w i + m i n ( w i ) + ( n + 1 ) ∗ x \Sigma wi+(n-2)*min(wi)+2*(min(wi)+x)-(n-1)*(min(wi)-x)=\Sigma wi+min(wi)+(n+1)*x Σwi+(n−2)∗min(wi)+2∗(min(wi)+x)−(n−1)∗(min(wi)−x)=Σwi+min(wi)+(n+1)∗x
可见x应选用整个输入中最小的元素。
综上,程序需要计算“借整体最小元素”与“不借元素”的两种情况,选出其中成本较小的一方。
AC代码如下
#include<iostream>
#include<algorithm>
using namespace std;
static const int MAX = 1000;
static const int VMAX = 10000;
int n, A[MAX], s;
int B[MAX], T[VMAX+1];
int solve(){
int ans = 0;
bool V[MAX];
for(int i = 0; i < n; i++){
B[i] = A[i];
V[i] = false;
}
sort(B, B+n);
for(int i = 0; i < n; i++) T[B[i]] = i;
for(int i = 0; i < n; i++){
if(V[i]) continue;
int cur = i;
int S = 0;
int m = VMAX;
int an = 0;
while(1){
V[cur] = true;
an++;
int v = A[cur];
m = min(m, v);
S += v;
cur = T[v];
if(V[cur]) break;
}
ans += min(S + (an - 2) * m, m + S + (an + 1) * s);
}
return ans;
}
int main(){
cin>>n;
s = VMAX;
for(int i = 0; i < n; i++){
cin>>A[i];
s = min(s, A[i]);
}
int ans = solve();
cout<<ans<<endl;
return 0;