小明目前在做一份毕业旅行的规划。打算从北京出发,分别去若干个城市,然后再回到北京,每个城市之间均乘坐高铁,且每个城市只去一次。由于经费有限,希望能够通过合理的路线安排尽可能的省一些路上的花销。给定一组城市和每对城市之间的火车票的价钱,找到每个城市只访问一次并返回起点的最小车费花销。
输入描述:
城市个数n(1<n≤20,包括北京, 北京为城市0) 城市间的车票价钱 n行n列的矩阵 m[n][n]
输出描述:
最小车费花销 s
示例:
输入:
4 0 2 6 5 2 0 4 4 6 4 0 2 5 4 2 0
输出:
13
思路:
dp[0][{1,2,3}]表示从城市0出发,经过城市1,2,3并返回城市0需要的最少车费,那么该问题可以分解为:
dp[0][{1,2,3}] = min(cost(0,1)+ dp[1][{2,3}], cost(0,2) + dp[2][{1,3}], cost(0,3) + dp[3][{1,2}]),表示先从城市0到城市1,在从城市1出发,经过城市2,3并返回城市0的车费、
先从城市0到城市2,在从城市2出发,经过城市1,3并返回城市0的车费、先从城市0到城市3,在从城市3出发,经过城市1,2并返回城市0的车费,这3个里的最小值就是最后的答案。
把思路转化成代码需要解决两个问题:
1.在dp数组中{1,2,3}这种城市的集合怎么表示
2.在选好出发城市后,怎么把这和城市在城市集合中删除
针对问题1:对城市集合用二进制编码,{1,2,3}表示为111=7,{2,3}表示为110=6,{1,3}表示为101=5,以此类推
针对问题2:根据问题1的编码方式,删除一个城市就是把它在二进制编码中对应的位置从1变成0,可以通过把1左移对应的位数,然后做异或实现
代码:
import java.util.*; public class Main{ public static void main(String[] args){ Scanner sc = new Scanner(System.in); int n = sc.nextInt(); int[][] c = new int[n][n]; for(int i = 0; i < n; i++){ for(int j = 0; j < n; j++){ c[i][j] = sc.nextInt(); } } int v = 1 << (n - 1); // 城市集合二进制编码的范围是0-(1 << (n - 1)) - 1 int[][] dp = new int[n][v]; for(int i = 0; i < n; i++){ dp[i][0] = c[i][0]; // base case,从城市i出发,不经过其他城市,最后返回城市0,那最少车费就是i到0的费用 } for(int j = 1; j < v; j++ ){ // 注意,这里的遍历方向是竖着遍历的,因为根据动态规划的数组遍历原则,遍历的终点是最后的答案,而这道题终点是dp[0][v-1],也就是数组的右上角,而且在v-1这列,只有第0行会存值,所以可以对每一列从上到下遍历 for(int i = 0; i < n; i++){ if(((j >> (i - 1)) & 1) == 0){ // 起点城市不在需要的城市集合里才能继续,因为对于dp[1][{1,2,3}]表示从1出发,经过1,2,3后回到0,城市1被重复经过,不符合题目要求 dp[i][j] = Integer.MAX_VALUE; for(int k = 1; k < n; k++){ if(((j >> (k - 1)) & 1) == 1){ // 表示要选的新的起点,在城市集合里 dp[i][j] = Math.min(dp[i][j], c[i][k] + dp[k][j ^ (1 << (k - 1))]); } } } } } System.out.println(dp[0][v - 1]); } }