小明目前在做一份畢業旅行的規划。打算從北京出發,分別去若干個城市,然后再回到北京,每個城市之間均乘坐高鐵,且每個城市只去一次。由於經費有限,希望能夠通過合理的路線安排盡可能的省一些路上的花銷。給定一組城市和每對城市之間的火車票的價錢,找到每個城市只訪問一次並返回起點的最小車費花銷。
輸入描述:
城市個數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]); } }