这东西其实蛮像最小斯坦纳树,不过我们通过状压DP的思想来讲一讲这个题。
形式化题意:考虑一共有 \(n\) 个点,\(m\) 条无向边,你需要从图中选出至多 \(K\) 条闭合路径,使得所有 \(w\) 个关键点都被覆盖,使得最长路径最小。
容易发现 \(n \le 500\),因此我们可以通过 Floyd 跑出所有点对 \(u, \, v\) 之间的最短路径长度 \(dist(u, \, v)\),我们发现,有用的点的个数只有 \(w\) 个,因此我们存下所有的 \(p_i\) 之后,只有 \(dist(p_i, \, p_j)\) 才是有价值的,因此我们的状态数不会太多。
接下来我们考虑怎么选出一条闭合路径,设 \(d[i][S]\) 表示从 \(1\) 开始,不重复的走到第 \(i\) 个关键点,经过关键点的集合为 \(S\) 的最短路径长度,容易得到
我们枚举点的顺序决定了我们的 \(d\) 的值一定是一条路径,而不是一颗树。
然后我们考虑 \(ring[S]\) 表示经过集合 \(S\) 中所有关键点的最短闭合路径长度,它的转移也是直观的
如此,我们把所有的闭合路径处理出来后,我们就可以进行我们答案的转移了。
我们定义 \(dp[i][S]\) 表示我们选择了 \(i\) 条闭合路径,覆盖的关键点集为 \(S\) 的最长路径的最小值,那么有
设所有关键点的集合总和是 \(P\),于是我们的答案可以表示为
Floyd 跑闭包复杂度是 \(O(n^3)\),最短路径长度更新是 \(O(w^22^w)\),选一条闭合路径的复杂度是 \(O(w2^w)\),子集枚举动规的复杂度是 \(O(k3^w)\),极限数据下,\(k\) 与 \(w\) 同阶,于是总时间复杂度为 \(O(n^3 + (w + w^2)2^w + w3^w)\),计算可得 \(500^3 + (14 + 14^2) \times 2^{14} + 14 \times 3^{14} = 195402206\),最慢的一步是 Floyd 闭包,可以通过。
参考代码
#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 16, B = 1 << 16;
int n, m, K, W;
int p[N];
int e[N][N], dis[M][B], ring[B], dp[M][B];void solve() {cin >> n >> m >> W >> K;memset(e, 0x3f, sizeof e);for (int i = 1; i <= n; i ++ ) e[i][i] = 0;for (int i = 1; i <= m; i ++ ) {int a, b, c;cin >> a >> b >> c;e[a][b] = e[b][a] = c;}for (int k = 1; k <= n; k ++ ) {for (int i = 1; i <= n; i ++ ) {for (int j = 1; j <= n; j ++ ) {e[i][j] = min(e[i][j], e[i][k] + e[k][j]);}}}memset(dis, 0x3f, sizeof dis);memset(ring, 0x3f, sizeof ring);memset(dp, 0x3f, sizeof dp);for (int i = 0; i < W; i ++ ) {cin >> p[i];dis[i][1 << i] = e[1][p[i]];}for (int i = 0; i < W; i ++ ) {for (int j = 0; j < W; j ++ ) {for (int k = 0; k < 1 << W; k ++ ) {if (!(k >> i & 1)) continue;if (k >> j & 1) continue;dis[j][k ^ (1 << j)] = min(dis[j][k ^ (1 << j)], dis[i][k] + e[p[i]][p[j]]);}}}ring[0] = 0;for (int i = 0; i < W; i ++ ) {for (int j = 1; j < 1 << W; j ++ ) {if (j >> i & 1) {ring[j] = min(ring[j], dis[i][j] + e[1][p[i]]);}}}for (int i = 0; i < 1 << W; i ++ ) dp[1][i] = ring[i];for (int S = 1; S < 1 << W; S ++ ) {for (int i = 2; i <= K; i ++ ) {for (int T = S & (S - 1); T; T = (T - 1) & S) {dp[i][S] = min(dp[i][S], max(dp[i - 1][T], ring[S ^ T]));}}}int ans = 1e9;for (int i = 1; i <= K; i ++ ) ans = min(ans, dp[i][(1 << W) - 1]);cout << ans << "\n";
}int main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);int T = 1;// cin >> T;while (T -- ) solve();return 0;
}