[Dijkstra贪心思想妙用]真实笔试题:传送门

* @description: 西西所在的国家有N座城市,每座城市有一道传送门,城市i的传送门通往城市a[i]
*               当西西位于城市i时,每次他可以执行以下三种操作中的一种:
*                   花费A的费用,从城市i前往城市a[i];
*                   如果a[i]>1,可以花费B的费用,将a[i]的值减少1;
*                   如果a[i]<N,可以花费C的费用,将a[i]的值增加1。
*               现在,西西想从城市1前往城市N,那么他至少花费多少费用?
*
*               输入:
*               第一行输入四个整数 N、A、B、C(1<=N<=10000,1<=A,B,C<=100000)
*               第二行输入N个整数,a[1]到a[N](1<=a[i]<=N)
*
*               输出:
*               输出一个整数,表示从城市1前往城市N所花费的最少费用
*
*               样例输入
*               7 1 1 1
*               3 6 4 3 4 5 6
*
*               样例输出
*               4
*
*               样例解释:
*               将a[1]减少1,此时a[1]=2*               从城市1前往城市2;
*               将a[2]增加1,此时a[2]=7;
*               从城市2前往城市7

这题拿到手第一反应是dp,但在列了一下例子之后,发现了其中的规律,做着做着发现跟Dijkstra的贪心思想竟然几乎完全吻合。

首先定义一个shortest[]数组,shortest[i]表示城市i到城市N的最小花费。并维护一个OPEN集合和CLOSE集合,记录还未确定最小花费的城市和已经确定的城市。

以样例为例,首先shortest[N] = 0,将N加入CLOSE,其他全在OPEN集合中,并计算每一个城市通过自增或自减操作+移动,到达N的花费

7 1 1 1
3 6 4 3 4 5 6

shortest = [4C+A,C+A,3C+A,4C+A,3C+A,2C+A,0]

从中选择一个或多个在OPEN集合中,并且shortest值最小的城市,此时这个值,就是最终的shortest的值。

这是一个很关键的结论,Dijkstra的关键思想也是在每一轮求出一个节点的最短路径时,就把求得的最短路径当作最终的最短路径。

在这题中,可以通过反证来想一想,记当前shortest值最小的城市为城市i,如果这个节点当前的值不是最终的值,那么城市i一定要先移动到其他城市,再移动到城市N,那么由于其他城市的shortest的值均大于他,且移动一次的花费是A。假设城市i通过移动到城市j再移动到城市N,那么花费为A+shortest[j],由于shortest[j] >= shortest[i],且A>0,那么A+shortest[j]一定大于shortest[i],也就是说当前的shortest[i]一定是最优值。

那么理解了这一步,一切就很容易了,将当前shortest值最小的城市,加入到CLOSE表里,当作下一轮的中间节点。

我在这里稍微优化了一下,每一次更新CLOSE表的时候,将CLOSE表先清空,原因是因为CLOSE表中原来的城市已经考虑过了,后续不需要再计算。

再下一轮迭代的时候,就将所有CLOSE表中的城市当作中间城市,再更新一遍shortest数组就可以了。

当CLOSE表中出现城市1的时候,结束循环,因为我们只需要知道shortest[1]就行了。

    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int N;
        long A,B,C;
        N = sc.nextInt();
        A = sc.nextLong();
        B = sc.nextLong();
        C = sc.nextLong();
        int[] a = new int[N];
//al相当于CLOSE表,no相当于OPEN表 HashSet
<Integer> al = new HashSet<>(); HashSet<Integer> no = new HashSet<>(); for(int i = 0; i < N; i++){ a[i] = sc.nextInt(); no.add(i+1); } long[] shortest = new long[N]; for(int i = 0; i < N; i++){ shortest[i] = A + C * (N - a[i]); } no.remove(N); al.add(N); shortest[N-1] = 0; while(!al.contains(1)){ long min = Long.MAX_VALUE; for(Integer tar : al){ for(Integer st : no){ if(a[st-1] == tar){ shortest[st-1] = Math.min(shortest[tar-1] + A,shortest[st-1]); } else if(a[st-1] > tar){ shortest[st-1] = Math.min(shortest[tar-1] + (a[st-1] - tar) * B + A,shortest[st-1]); } else{ shortest[st-1] = Math.min(shortest[tar-1] + (tar - a[st-1]) * C + A,shortest[st-1]); } min = Math.min(min,shortest[st-1]); } } al.clear(); Iterator<Integer> iterator = no.iterator(); while (iterator.hasNext()){ Integer in = iterator.next(); if (shortest[in-1] == min){ al.add(in); iterator.remove(); } } } System.out.println(shortest[0]); }

 表述的可能不是很清楚,如有疑问可以评论指出。

posted @ 2020-04-20 18:19  咕咕刘三刀  阅读(351)  评论(0编辑  收藏  举报