Description
给定一张 nnn 点 mmm 边的无向连通图,其中有 kkk 个点是关键点。求图的一个子图,满足包含了所有 kkk 个关键点,使得所包含的边集的权值和最小,求这个最小值。
Analysis
首先,答案子图肯定是一棵树,原因显然。
由于 kkk 很小,考虑状压dp,设 dpu,Sdp_{u,S}dpu,S 表示以 uuu 为根,关键点集合为 SSS 时的答案,则有两种情况:
- uuu 的度数为 111,则可以删除 uuu,找一个相邻的节点 vvv 来替代,有 dpu,S=dpv,S+w(u,v)dp_{u,S}=dp_{v,S}+w(u,v)dpu,S=dpv,S+w(u,v),这里我们用
SPFA跑最短路实现转移(这里用SPFA优于Dijkstra). - uuu 的度数大于 111,可以将 SSS 分成两个集合 A,BA,BA,B,有:dpu,S=dpu,A+dpu,Bdp_{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;
}
9875

被折叠的 条评论
为什么被折叠?



