题目的N这么小,当然是选择用状压DP啦! 等等,我好像不会状缩。。。。
首先,我们当然是要写状态转移方程了!!
那么,如果我们设 f[s] 状态s下,所要的最小花费,那么很显然有状态转移方程:
(s为总集合)
f[(1 << j) | s] = f[s] + num[i] * dis[i][j] (从 i 点走向 j点时)
其中,num表示之前已经走过的点的数量,dis表示从i到j的边的距离(这不就是题目要求吗)
很显然,这道题用DFS来做比较方便(跑图和回溯)
等等。
如果我们用不同的走法更新到同一个点,那么是否会影响到我们的更新呢? 显然是会的。
就比如,我们 1 -› 2 -› 3 -› 4 和 1 -› 3 -› 5 -› 2 - › 4 ,两种走法下,num的不同显然会给我们的更新带来不同的影响。
所以,上面的DP走法是有后效性的。
于是,我们要更新DP方程:
f[1 << j | s] = min( best + num[i] * dis[i][j]) , 其中 best 为上一层的最优解。
注意,起点不确定,怎么办? 当然是枚举起点啦!
#include <bits/stdc++.h> using namespace std; #define N 13 #define isdigit(c) ((c)>='0'&&(c)<='9') #define min(a,b) ((a)<(b)?(a):(b)) inline int read(){ int x = 0, s = 1; char c = getchar(); while(!isdigit(c)){ if(c == '-')s = -1; c = getchar(); } while(isdigit(c)){ x = (x << 1) + (x << 3) + (c ^ '0'); c = getchar(); } return x * s; } int f[N][1 << N][N]; int num[N], dis[N][N]; int n, m, limit; int ans = (int)2e9; /* 状态转移方程: 对于当前的这个点j,如果他处在第 deth 层, 从第i号点转移到 j 则有 f[j][1 << j | s][deth] = best + num[i] * dis[i][j] best 表示上一层最优解,dis表示i和j之间的距离 */ void dfs(int s, int best, int deth){ if(best >= ans) return ; /*剪枝*/ if(s == limit) { ans = min(ans, best); /*已经走完所有点*/ return ; } for(int i = 0;i < n; i++){ if(!(1 << i & s)) continue; /*这个点还未走过,不能转移*/ for(int j = 0;j < n; j++){ if(!(1 << j & s) && dis[i][j] < (int)2e9){ if(f[j][1 << j | s][deth + 1] > best + num[i] * dis[i][j]){ f[j][1 << j | s][deth + 1] = best + num[i] * dis[i][j]; /*进行转移*/ num[j] = num[i] + 1; /*deth + 1, 因为j在i的下一层*/ dfs(1 << j | s, f[j][1 << j | s][deth + 1], deth + 1); } } } } return ; } int main(){ memset(dis, 127, sizeof(dis)); n = read(), m = read(); limit = (1 << n) - 1; for(int i = 1;i <= m; i++){ int x = read() - 1, y = read() - 1, w = read(); /*编号减一, 给状压用(2 ^ 0 才是 1)*/ dis[x][y] = dis[y][x] = min(dis[x][y], w); } for(int i = 0;i < n; i++){ memset(num, 0, sizeof(num)); memset(f, 127, sizeof(f)); num[i] = 1; dfs(1 << i, 0, 0); /*1 << i 把第i位设为 1*/ } printf("%d ", ans); return 0; }