把已访问顶点压成二进制集合,设 dp[mask][u] 表示走过 mask 且停在 u 时的最大路程。
OJ: luogu
题目 ID: P1294
难度:普及+/提高
标签:动态规划状态压缩图论
日期: 2026-06-19 19:33
题意
给出一张无向带权图,可以从任意点出发、任意点结束,但同一个点不能重复经过。
要求求出能够走出的最大总路程。
思路
最直接的办法是从每个点出发做 DFS,枚举所有简单路径。
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
static vector<pair<int, int>> g[25];
static int vis[25];
static int ans = 0;
void dfs(int u, int dist) {
ans = max(ans, dist);
for (auto [v, w] : g[u]) {
if (vis[v]) {
continue;
}
vis[v] = 1;
dfs(v, dist + w);
vis[v] = 0;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
for (int i = 1; i <= n; ++i) {
memset(vis, 0, sizeof(vis));
vis[i] = 1;
dfs(i, 0);
}
cout << ans << '\n';
return 0;
}brute.cpp 用访问标记数组直接回溯,适合小图对拍。
由于
表示当前已经访问过集合
这张图展示样例图的结构:
graph G {
1 -- 2 [label="10"];
2 -- 3 [label="20"];
3 -- 4 [label="30"];
4 -- 1 [label="40"];
1 -- 3 [label="50"];
2 -- 4 [label="60"];
}
从图里可以看出,最长简单路径其实就是依次经过四个点并把三条较优边串起来。状态压缩 DP 正是在系统枚举“走过哪些点、最后停在哪”的所有可能。
初始时任意单点都能作为起点:
如果当前在
所有状态里的最大值就是答案。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
const int MAXS = 1 << 20;
static int dp[MAXS][MAXN];
static vector<pair<int, int>> g[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
--u, --v;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
memset(dp, -1, sizeof(dp));
for (int i = 0; i < n; ++i) {
dp[1 << i][i] = 0;
}
int ans = 0;
int full = 1 << n;
for (int mask = 1; mask < full; ++mask) {
for (int u = 0; u < n; ++u) {
if (dp[mask][u] == -1) {
continue;
}
ans = max(ans, dp[mask][u]);
for (auto [v, w] : g[u]) {
if ((mask >> v) & 1) {
continue;
}
int nmask = mask | (1 << v);
dp[nmask][v] = max(dp[nmask][v], dp[mask][u] + w);
}
}
}
cout << ans << '\n';
return 0;
}复杂度
状态数约为
总结
这题的关键不是死搜,而是看出
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
