题面
大秦 为你打开 题目传送门
题目翻译:
有 $n$ 个地方(标号 $1$ 到 $n$ )要从标号为 $0$ 的地方出去,经过所有的地方之后回来,求最短的时间,输入 $(n+1)$ 的矩阵 $a$,$a[i][j]$表示顶点 $i$ 到顶点 $j$ 所需要的时间。
第一行输入一个整数 $n(1≤n≤10)$ 。
接下来 $𝑛+1$ 行,每行 $n+1$ 个整数,表示矩阵中的元素,矩阵中的元素在区间 $[1,1000]$ 内。
思路
这道题我们要同时兼顾经过了哪些点(因为要经过所有点),所以最好的方式就是把经过的点存下来,但是想要在递推过程中记录这些东西,有点困难。发现数据范围只有 $10$ 不难想到状压DP。因为状压DP是基于集合的DP,每经过一个点,就在状态中标记。
另外要处理某两个点之间的最短路,因为只有 $10$ 的数据范围,所以这个可以用 floyd 水水过。
此外,在标记每一个点的是否经过,还有一个很重要的元素是最后一个点是哪里。就比如说:
同样是经过了点 1 2 3 4
但是顺序不一样,路程就可能有变化,另外我们要从最后一个经过的点往后走,所以也是要记录最后一个经过的点的重要原因。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include<bits/stdc++.h> using namespace std;
int n; int a[20][20];
int dp[4010][20];
void Input() { scanf("%d", &n); n++; for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { scanf("%d", &a[i][j]); } } }
void Init() { for(int i = 0; i < n; i++) { for(int j = 0; j < n; j++) { for(int k = 0; k < n; k++) { a[i][j] = min(a[i][j], a[i][k] + a[k][j]); } } } }
void Work() { Init(); memset(dp, -1, sizeof(dp)); dp[1][0] = 0; for(int mask = 0; mask < (1 << n); mask++) { for(int ed = 0; ed < n; ed++) { if(dp[mask][ed] == -1) continue; for(int to = 0; to < n; to++) { if((1 << to) & mask) continue; int nmask = mask | (1 << to); if(dp[nmask][to] == -1 || dp[nmask][to] > dp[mask][ed] + a[ed][to]) { dp[nmask][to] = dp[mask][ed] + a[ed][to]; } } } } int ans = 1e9; for(int i = 1; i < n; i++) { ans = min(ans, dp[(1 << n) - 1][i] + a[i][0]); } printf("%d\n", ans); }
int main() { Input(); Work(); return 0; }
|