#include using namespace std; const int N = 15; const int INF = 0x01010101; int g[N][N]; // 图的邻接矩阵 int n, m; // n个节点,m条边 vector p[N]; // 第一维:哪一行;第二维:此行第几个是哪个点 bool st[N]; // 每个节点是否已使用 int res = INF; // 代价最小值,预求最小,先设最大 int micost[N]; // 每个点最小的代价,相对前一个贪心+暴搜,这个采用了一个估值函数,用于A*寻路 // 现在第u层,讨论k号节点,总共用了cnt个点,现在这棵树价值sum,剩余点的总最小估值代价 void dfs(int u, int k, int cnt, int sum, int r) { // 最优性剪枝 !!!最重要!!!只要现阶段不是最小的了就没有继续的必要了 // 假设本层u把剩余的所有节点全部覆盖,那么最小代价根据定义就是r*u,再加上以前积累的sum, // 总代价就将是sum+r*u,如果此最小代价仍然高于目前取得的小值,那么,此路径无用 if (sum + r * u >= res) return; if (p[u - 1].size() == 0) return; // 上一层没有选入点(上层为空),树不可能继续构建,该可能不合法,直接跳过 if (cnt == n) { res = min(sum, res); // 所有点都已经被构建进树中,更新答案,结束这种可能 return; } if (k > n) { dfs(u + 1, 1, cnt, sum, r); // 该层所有点都讨论过一遍,继续下一层 return; } if (st[k]) { dfs(u, k + 1, cnt, sum, r); // 该点已用,直接讨论下一个点 return; } // 选用此点(可能1) int cost = INF; // 找到该点与上层的最小联系(贪心) for (int i = 0; i < p[u - 1].size(); i++) // 若与上层所有点不连通,则mini是INF,会被最优性剪枝减去 cost = min(cost, g[p[u - 1][i]][k]); st[k] = 1; // 能算到这里,该点没用过,用下试试 p[u].push_back(k); dfs(u, k + 1, cnt + 1, sum + cost * u, r - micost[k]); p[u].pop_back(); st[k] = 0; // 回溯 // 不用此点(可能2) dfs(u, k + 1, cnt, sum, r); } int main() { memset(g, 0x3f, sizeof g); memset(micost, 1, sizeof micost); scanf("%d %d", &n, &m); while (m--) { int a, b, c; scanf("%d %d %d", &a, &b, &c); g[a][b] = g[b][a] = min(g[a][b], c); // 无向图双向赋值,只取最小值 // 记录接入a,b的最少代价,用于估值 micost[a] = min(micost[a], c); micost[b] = min(micost[b], c); } // 整体最小估值 int r = 0; for (int i = 1; i <= n; i++) r += micost[i]; for (int i = 1; i <= n; i++) { // 每个点都当一次根 st[i] = 1; // 点已用 p[0].push_back(i); // 根看做第0层 dfs(1, 1, 1, 0, r - micost[i]); // 从第一层开始搜索,考查第1个节点,目前使用了节点个数为1,总代价为0,最小估值为sum-micost[i] p[0].pop_back(); st[i] = 0; // 以该点为根的数搜索完毕 } cout << res << endl; return 0; }