* @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]); }
表述的可能不是很清楚,如有疑问可以评论指出。