考点
题解
见思路
思路
一开始以为是贪心,但很容易举个反例:如果有多个距离一样的奶酪,该走哪个确保整体最优呢?
很明显也不是单纯的DFS或BFS:
- 最大有15个奶酪,也就是说纯粹用DFS高达15的阶乘...再怎么剪枝也会炸
- 也不是BFS,BFS是找步骤最低的解...这里要找解里面总距离最小的
那只有DP一种可能了
微观上看,每个合法的序列都可以拆分成子序列+结尾元素,比如序列{1, 2, 3, 4, 5} = {1, 2, 3, 4} + {5}
最多只有15个元素,而int
有32位,所以可以将子序列映射为整数,以状态压缩来达成DP
内存限制是106数量级,故而状态压缩不能超过220这一极限
这样并不能保存元素的前后顺序,所以每次只保存,相同组成的序列中总距离最小的
递推
根据上述思路,很容易写出状态转移方程: \[
\min \left( dp\left[ i \right] \left[ j \right] ,dp\left[ i\oplus
(1<<(j-1)) \right] \left[ k \right] +dis\left( j,k \right) \right)
\rightarrow dp\left[ i \right] \left[ j \right]
\]
i
代表当前序列,以j
结尾;而当前序列是由某个不包含j
,以k
结尾的子序列在末尾添加j
组成的
你可能会好奇为什么是1 << (j - 1)
而不是1 << j
,这样不是占了最低位属于起点的位置吗?
因为起点必须包含在序列且只能出现在开头,边界值初始化时可以一次性处理
如果把最低位空着,会导致部分状态转移遇到跳过起点的情况导致报错
我调了10多个小时带来的教训
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 57 58 59 60 61 62
| #include <bits/stdc++.h> using namespace std; const int LEN = 16; int N; double ans = INT_MAX, table[LEN][LEN], dp[1 << LEN][LEN];
struct Node { double x_, y_; Node(double x = 0, double y = 0) : x_(x), y_(y) {} } arr[LEN];
double dis(Node &a, Node &b) { double x1 = a.x_, y1 = a.y_, x2 = b.x_, y2 = b.y_; return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); }
void init() { for (int i = 0; i <= N - 1; ++i) for (int j = i + 1; j <= N; ++j) table[i][j] = table[j][i] = dis(arr[i], arr[j]); memset(dp, 127, sizeof(dp)); dp[0][0] = 0; for (int i = 1; i <= N; ++i) dp[1 << (i - 1)][i] = table[0][i]; }
int main() { cin >> N; for (int i = 1; i <= N; ++i) cin >> arr[i].x_ >> arr[i].y_; init(); for (int i = 1; i < (1 << N); ++i) { for (int j = 1; j <= N; ++j) { if (i & (1 << (j - 1)) == 0) continue; for (int k = 1; k <= N; ++k) { if (k == j) continue; dp[i][j] = min(dp[i][j], dp[i ^ (1 << (j - 1))][k] + table[j][k]); } } } for (int i = 1; i <= N; ++i) { ans = min(ans, dp[(1 << N) - 1][i]); } cout << fixed << setprecision(2) << ans; return 0; }
|
记忆化搜索
如果用递归实现,就方便很多了....因为能直接记录上个序列及其结尾元素
每次判断应该添加什么元素到上次序列的末尾:
如果之前有记录本次前后搭配
若之前的搭配值更小,保留原有的
若之前的搭配值更大,用当前搭配更新
若无记录,记录本次搭配
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 57 58 59 60 61 62 63 64 65 66 67 68
| #include <bits/stdc++.h> using namespace std; const int LEN = 16; int N, vis[LEN]; double ans = INT_MAX, table[LEN][LEN], mem[1 << LEN][LEN];
struct Node { double x_, y_; Node(double x = 0, double y = 0) : x_(x), y_(y) {} } arr[LEN];
double dis(Node &a, Node &b) { double x1 = a.x_, y1 = a.y_, x2 = b.x_, y2 = b.y_; return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); }
void init() { for (int i = 0; i <= N - 1; ++i) for (int j = i + 1; j <= N; ++j) table[i][j] = table[j][i] = dis(arr[i], arr[j]); }
void dfs(int pre, int qe, double sum, int cnt) { if (sum >= ans) return; if (cnt > N) { ans = min(ans, sum); return; } for (int i = 1; i <= N; ++i) { if (vis[i]) continue; double nxt_sum = sum + table[pre][i]; if (mem[qe][i] && mem[qe][i] <= nxt_sum) continue; vis[i] = 1; mem[qe][i] = nxt_sum; dfs(i, qe | (1 << (i - 1)), nxt_sum, cnt + 1); vis[i] = 0; } }
int main() { cin >> N; for (int i = 1; i <= N; ++i) cin >> arr[i].x_ >> arr[i].y_; init(); dfs(0, 0, 0, 1); cout << fixed << setprecision(2) << ans; return 0; }
|