Description
给定一张 n n n 点 m m m 边的无向连通图,其中有 k k k 个点是关键点。求图的一个子图,满足包含了所有 k k k 个关键点,使得所包含的边集的权值和最小,求这个最小值。
Analysis
首先,答案子图肯定是一棵树,原因显然。
由于
k
k
k 很小,考虑状压dp,设
d
p
u
,
S
dp_{u,S}
dpu,S 表示以
u
u
u 为根,关键点集合为
S
S
S 时的答案,则有两种情况:
-
u
u
u 的度数为
1
1
1,则可以删除
u
u
u,找一个相邻的节点
v
v
v 来替代,有
d
p
u
,
S
=
d
p
v
,
S
+
w
(
u
,
v
)
dp_{u,S}=dp_{v,S}+w(u,v)
dpu,S=dpv,S+w(u,v),这里我们用
SPFA
跑最短路实现转移(这里用SPFA
优于Dijkstra
). - u u u 的度数大于 1 1 1,可以将 S S S 分成两个集合 A , B A,B A,B,有: d p u , S = d p u , A + d p u , B dp_{u,S}=dp_{u,A}+dp_{u,B} dpu,S=dpu,A+dpu,B,可以通过枚举子集实现转移。
Code
// Problem: P6192 【模板】最小斯坦纳树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6192
// Memory Limit: 250 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <iostream>
#include <queue>
using namespace std;
#define int long long
using PII = pair<int, int>;
const int INF = 1e18;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int n, m, k;
cin >> n >> m >> k;
vector<vector<PII>> G(n);
for(int i = 0, u, v, w; i < m; i++){
cin >> u >> v >> w;
u--; v--;
G[u].emplace_back(v, w);
G[v].emplace_back(u, w);
}
int up = 1 << k;
vector<vector<int>> dp(n, vector<int>(up, INF));
vector<int> p(k);
for(int i = 0; i < k; i++){
cin >> p[i];
p[i]--;
dp[p[i]][1 << i] = 0;
}
queue<int> q;
vector<bool> vis(n, false);
auto spfa = [&](int state){
while(q.size()){
int u = q.front();
q.pop();
vis[u] = false;
for(auto edge: G[u]){
int v = edge.first, w = edge.second;
if(dp[v][state] > dp[u][state] + w){
dp[v][state] = dp[u][state] + w;
if(!vis[v]){
q.push(v);
vis[v] = true;
}
}
}
}
};
for(int s1 = 1; s1 < up; s1++){
for(int i = 0; i < n; i++){
for(int s2 = s1 & (s2 - 1); s2; s2 = s1 & (s2 - 1))
dp[i][s1] = min(dp[i][s1], dp[i][s2] + dp[i][s1 ^ s2]);
if(dp[i][s1] != INF){
q.push(i);
vis[i] = true;
}
}
spfa(s1);
}
cout << dp[p[0]][up - 1] << endl;
return 0;
}