P6192 【模板】最小斯坦纳树 题解

Description

给定一个包含 nn 个结点和 mm 条带权边的无向连通图 G=(V,E)G=(V,E)

再给定包含 kk 个结点的点集 SS,选出 GG 的子图 G=(V,E)G'=(V',E'),使得:

  1. SVS\subseteq V'

  2. GG' 为连通图;

  3. EE' 中所有边的权值和最小。

你只需要求出 EE' 中所有边的权值和。

1n100,  1m500,  1k10,  1u,vn,  1w1061\leq n\leq 100,\ \ 1\leq m\leq 500,\ \ 1\leq k\leq 10,\ \ 1\leq u,v\leq n,\ \ 1\leq w\leq 10^6

Solution

求出的图一定是树,因为如果有环删掉环边一定更优。

假设已经求出了这个树,那么对于树上的每个点 uu,可以记录 SS 表示 uu 的子树出现的关键点集合。利用这个集合可以在图上模拟选树的儿子的过程。

fi,Sf_{i,S} 表示图上与 ii 连通的树包含 SS 集合对应的关键点的最小边权和,可以得到两种转移:

  1. ii 在树上的儿子有 2\geq 2 个或者 ii 为关键点,则儿子的集合一定小于 SS 且互不相交,所以可以直接枚举儿子的集合然后求并就是答案。
  2. ii 在树上只有一个儿子且 ii 不为关键点,转移一定形如 fi,Sfj,S+wf_{i,S}\leftarrow f_{j,S}+w,跑 dijkstra 即可。

对于第一种转移实现上有一些细节,如果每次是先枚举 SSii 再枚举 ii 的邻域的状态合并,这样单次就是 O(3S)O(3^{|S|})。正确做法是直接让 fi,Sfi,ST+fi,Tf_{i,S}\leftarrow f_{i,S-T}+f_{i,T},因为 <S<S 的答案已经确定。这样做转移一的总时间复杂度就是 O(3kn)O(3^kn) 了。

容易发现这么 dp 不会出现不合法的情况,并且最优解一定会被算到。

时间复杂度:O(3kn+2kmlogm)O(3^kn+2^km\log m)

Code

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
69
70
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 105, kMaxS = (1 << 10);

int n, m, k;
int f[kMaxN][kMaxS], id[kMaxN];
std::vector<std::pair<int, int>> G[kMaxN];

void dijkstra(int s) {
static bool vis[kMaxN];
std::priority_queue<std::pair<int, int>> q;
for (int i = 1; i <= n; ++i)
q.emplace(-f[i][s], i), vis[i] = 0;
for (; !q.empty();) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto [v, w] : G[u]) {
if (f[v][s] > f[u][s] + w) {
f[v][s] = f[u][s] + w;
q.emplace(-f[v][s], v);
}
}
}
}

void solve() {
memset(f, 0x3f, sizeof(f));
for (int i = 1; i <= n; ++i) f[i][id[i] ? (1 << (id[i] - 1)) : 0] = 0;
for (int s = 0; s < (1 << k); ++s) {
for (int i = 1; i <= n; ++i) {
for (int t = s; t; t = (t - 1) & s)
f[i][s] = std::min(f[i][s], f[i][s ^ t] + f[i][t]);
}
dijkstra(s);
}
}

void dickdreamer() {
std::cin >> n >> m >> k;
for (int i = 1; i <= m; ++i) {
int u, v, w;
std::cin >> u >> v >> w;
G[u].emplace_back(v, w), G[v].emplace_back(u, w);
}
for (int i = 1; i <= k; ++i) {
int x;
std::cin >> x;
id[x] = i;
}
solve();
int ans = 1e9;
for (int i = 1; i <= n; ++i) ans = std::min(ans, f[i][(1 << k) - 1]);
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P6192 【模板】最小斯坦纳树 题解
https://sobaliuziao.github.io/2024/08/30/post/95b142b.html
作者
Egg_laying_master
发布于
2024年8月30日
许可协议