最小斯坦纳树
老是忘掉这个东西,稍微记一下。
不难观察得到最终答案是一棵树。
涉及到树形的 dp 和状压,考虑状态 \(dp(u, S)\) 表示 \(u\) 为树根,目前连接起来的点状态为 \(S\) 的最小代价。
转移分两种:
- 树根 \(u\to v\):\(dp(v,S)\gets dp(u,S) + dis(u,v)\)。
- 两棵子树合并:\(dp(u,S)\gets dp(u,T)+dp(u,S-T)\)。
考虑按大小顺序枚举 \(S\),第一种相当于最短路,第二种枚举子集,时间复杂度 \(\mathcal O(nm2^k+n3^k)\)。用了 SPFA 求最短路。
#include <bits/stdc++.h>
#define pb emplace_back
#define fir first
#define sec second
using i64 = long long;
using u64 = unsigned long long;
using pii = std::pair<int,int>;
const i64 mod = 998244353;
const int maxn = 505;
const int maxm = 1 << 10;
int f[maxm][maxn], n, m, k;
bool inq[maxn];
std::vector<pii> G[maxn];
std::queue<int> q;
int main() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
scanf("%d %d %d", &n, &m, &k);
for(int i = 1;i <= m;++ i) {
int u, v, t;
scanf("%d %d %d", &u, &v, &t);
G[u].pb(v, t);
G[v].pb(u, t);
}
memset(f, 0x3f, sizeof(f));
for(int i = 1;i <= k;++ i) {
int x;
scanf("%d", &x);
f[1 << (i - 1)][x] = 0;
}
for(int S = 1;S < (1 << k);++ S) {
for(int T = (S - 1) & S;T;T = (T - 1) & S) {
for(int i = 1;i <= n;++ i)
f[S][i] = std::min(f[S][i], f[T][i] + f[S ^ T][i]);
}
for(int i = 1;i <= n;++ i)
q.emplace(i), inq[i] = true;
int mn = 0x3f3f3f3f, pos = 0;
while(!q.empty()) {
int u = q.front();
q.pop();
inq[u] = false;
for(auto& [v, w] : G[u])
if(f[S][v] > f[S][u] + w) {
f[S][v] = f[S][u] + w;
if(!inq[v])
inq[v] = true, q.emplace(v);
}
}
}
int ans = 0x3f3f3f3f;
for(int i = 1;i <= n;++ i)
ans = std::min(f[(1 << k) - 1][i], ans);
printf("%d\n", ans);
return 0;
}