Minimum Cost Sort(Java实现)

题目描述

有n个数,你现在需要将这n个数按升序排列。排序过程中每次交换两个数字需要花费代价为 w i + w j w_i+w_j wi+wj。现在请你写一个程序,要求花费最小的代价将其排序。

Input

第一行为一个正整数n,第二行为n个数 w i ( i = 0 , 1 , ⋅ ⋅ ⋅ n − 1 ) w_i(i=0,1,···n-1) wi(i=0,1,n1)

Output

打印最小代价

Sample Input

5
1 5 3 4 2

Sample Output

7

实现思路

这题还是比较考验思维能力的。对于一个乱序的数组彼此之间交换可以形成一个闭环的圆。这里举 4 , 2 , 3 , 7 , 1 , 6 , 5 4,2,3,7,1,6,5 4,2,3,7,1,6,5的例子,如图所示。

在这里插入图片描述
所以我们有以下流程:

  • 长度为1的圆,即自己和自己交换,成本为0
  • 长度为2的圆,两个对换即可,成本为 w i + w j w_i+w_j wi+wj
  • 长度大于2的圆,我们可以选取这个长度中最小的元素作为踏板,通过最小的的元素来移动其他元素可以保证成本最小。如图 4 , 7 , 1 , 5 4,7,1,5 4,7,1,5
    在这里插入图片描述
    此时总代价为:在这里插入图片描述
    其中 A [ i ] A[i] A[i]是除去不需要移动的元素以外剩余的元素。这里假设长度大于2的圆有n个元素,每个元素至少移动一次,所以 ∑ A [ i ] \sum A[i] A[i]。最小值移动了 n − 2 n-2 n2次(可以动手演算一下)。
    这只是其中一种情况,可能我们将圆内最小值做踏板的代价不如将圆外最小值做踏板的代价小:
    所以我们可以先选取整体的最小值 m i n ( w i ) min(w_i) min(wi)
    此时的总代价为:在这里插入图片描述
    我们来看一下代码的细节。
int ans = 0;
boolean[] V = new boolean[n];
for(int i=0;i<n;i++)
{
    B[i] = A[i];
    V[i] = false;
}
Arrays.sort(B,0,n);
for(int i=0;i<n;i++)
{
    T[B[i]] = i;
}

首先初始化答案ans,设置一个V数组记录元素是否加入圆环。将A数组的值赋给B数组,我们需要一个排序好的数组来确定元素排序好后的位置。所以 T [ B [ i ] ] T[B[i]] T[B[i]]记录了B[i]的位置。

for(int i=0;i<n;i++)
{
    if(V[i])
        continue;
    int cur = i;
    int sum = 0;
    int m = VMAX;
    int an = 0;
    while(true)
    {
        V[cur] = true;
        an++;
        int v = A[cur];
        m = Math.min(v,m);
        sum += v;
        cur = T[v];
        if(V[cur])
            break;
    }
    ans += Math.min(sum + (an-2)*m,m+sum+(an+1)*s);
}

这段代码是整个程序的核心。for从0到n-1是保证每个数都参与到圆中。if判断则如果当前元素已经在圆中就判断下一个元素。如果不在,就记录当前元素在A中的位置,然后while循环中开始寻找i的位置应该是什么元素,依此类推,直到形成了闭环,也就是V[cur] = true,即当前判断元素已经加入了圆就终止while循环。最终比较两种方案的最小值加入ans即可。

完整代码

import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;

public class MinimumCostSort {
    public static int MAX = 1000;
    public static int VMAX = 10000;
    static int[] B;
    static int[] A;
    static int[] T;
    public static int n;
    public static int s = VMAX;

    public static void main(String[] args) {
        B = new int[MAX];
        A = new int[MAX];
        T = new int[VMAX+1];
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        for(int i=0;i<n;i++)
        {
            A[i] = in.nextInt();
            s = Math.min(s,A[i]);
        }
        int ans = solve();
        System.out.println(ans);
    }
    public static int solve()
    {
        int ans = 0;
        boolean[] V = new boolean[n];
        for(int i=0;i<n;i++)
        {
            B[i] = A[i];
            V[i] = false;
        }
        Arrays.sort(B,0,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 sum = 0;
            int m = VMAX;
            int an = 0;
            while(true)
            {
                V[cur] = true;
                an++;
                int v = A[cur];
                m = Math.min(v,m);
                sum += v;
                cur = T[v];
                if(V[cur])
                    break;
            }
            ans += Math.min(sum + (an-2)*m,m+sum+(an+1)*s);
        }
        return ans;
    }
}

题目链接ALDS1_6_D

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值